STARK: Add lighting for actor models

This commit is contained in:
Bastien Bouclet 2015-09-14 15:26:09 +02:00
parent 5778902561
commit d471401da9
14 changed files with 277 additions and 41 deletions

View file

@ -37,7 +37,7 @@ namespace Gfx {
OpenGLSActorRenderer::OpenGLSActorRenderer(Driver *gfx) :
VisualActor(),
_gfx(gfx) {
static const char* attributes[] = { "position1", "position2", "bone1", "bone2", "boneWeight", "texcoord", nullptr };
static const char* attributes[] = { "position1", "position2", "bone1", "bone2", "boneWeight", "normal", "texcoord", nullptr };
_shader = Graphics::Shader::fromFiles("stark_actor", attributes);
}
@ -47,7 +47,7 @@ OpenGLSActorRenderer::~OpenGLSActorRenderer() {
delete _shader;
}
void OpenGLSActorRenderer::render(const Math::Vector3d position, float direction) {
void OpenGLSActorRenderer::render(const Math::Vector3d position, float direction, const LightEntryArray &lights) {
if (_meshIsDirty) {
// Update the OpenGL Buffer Objects if required
clearVertices();
@ -63,13 +63,24 @@ void OpenGLSActorRenderer::render(const Math::Vector3d position, float direction
Math::Matrix4 view = StarkScene->getViewMatrix();
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
Math::Matrix4 mvp = projection * view * model;
mvp.transpose();
Math::Matrix4 modelViewMatrix = view * model;
modelViewMatrix.transpose(); // OpenGL expects matrices transposed when compared to ResidualVM's
Math::Matrix4 projectionMatrix = projection;
projectionMatrix.transpose(); // OpenGL expects matrices transposed when compared to ResidualVM's
Math::Matrix4 normalMatrix = modelViewMatrix;
normalMatrix.invertAffineOrthonormal();
//normalMatrix.transpose(); // OpenGL expects matrices transposed when compared to ResidualVM's
//normalMatrix.transpose(); // No need to transpose twice in a row
_shader->use(true);
_shader->setUniform("mvp", mvp);
_shader->setUniform("modelViewMatrix", modelViewMatrix);
_shader->setUniform("projectionMatrix", projectionMatrix);
_shader->setUniform("normalMatrix", normalMatrix.getRotation());
setBoneRotationArrayUniform("boneRotation");
setBonePositionArrayUniform("bonePosition");
setLightArrayUniform("lights", lights);
Common::Array<MeshNode *> meshes = _model->getMeshes();
Common::Array<MaterialNode *> mats = _model->getMaterials();
@ -89,12 +100,13 @@ void OpenGLSActorRenderer::render(const Math::Vector3d position, float direction
GLuint vbo = _faceVBO[*face];
GLuint ebo = _faceEBO[*face];
_shader->enableVertexAttribute("position1", vbo, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), 0);
_shader->enableVertexAttribute("position2", vbo, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), 12);
_shader->enableVertexAttribute("bone1", vbo, 1, GL_FLOAT, GL_FALSE, 11 * sizeof(float), 24);
_shader->enableVertexAttribute("bone2", vbo, 1, GL_FLOAT, GL_FALSE, 11 * sizeof(float), 28);
_shader->enableVertexAttribute("boneWeight", vbo, 1, GL_FLOAT, GL_FALSE, 11 * sizeof(float), 32);
_shader->enableVertexAttribute("texcoord", vbo, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), 36);
_shader->enableVertexAttribute("position1", vbo, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 0);
_shader->enableVertexAttribute("position2", vbo, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 12);
_shader->enableVertexAttribute("bone1", vbo, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 24);
_shader->enableVertexAttribute("bone2", vbo, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 28);
_shader->enableVertexAttribute("boneWeight", vbo, 1, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 32);
_shader->enableVertexAttribute("normal", vbo, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 36);
_shader->enableVertexAttribute("texcoord", vbo, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(float), 48);
_shader->use(true);
_shader->setUniform("textured", tex != nullptr);
_shader->setUniform("color", Math::Vector3d(material->_r, material->_g, material->_b));
@ -131,7 +143,7 @@ void OpenGLSActorRenderer::uploadVertices() {
}
uint32 OpenGLSActorRenderer::createFaceVBO(const FaceNode *face) {
float *vertices = new float[11 * face->_verts.size()];
float *vertices = new float[14 * face->_verts.size()];
float *vertPtr = vertices;
// Build a vertex array
@ -149,11 +161,15 @@ uint32 OpenGLSActorRenderer::createFaceVBO(const FaceNode *face) {
*vertPtr++ = (*tri)->_boneWeight;
*vertPtr++ = (*tri)->_normal.x();
*vertPtr++ = (*tri)->_normal.y();
*vertPtr++ = (*tri)->_normal.z();
*vertPtr++ = -(*tri)->_texS;
*vertPtr++ = (*tri)->_texT;
}
uint32 vbo = Graphics::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(float) * 11 * face->_verts.size(), vertices);
uint32 vbo = Graphics::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(float) * 14 * face->_verts.size(), vertices);
delete[] vertices;
return vbo;
@ -224,5 +240,48 @@ void OpenGLSActorRenderer::setBoneRotationArrayUniform(const char *uniform) {
delete[] rotations;
}
void OpenGLSActorRenderer::setLightArrayUniform(const char *uniform, const LightEntryArray &lights) {
assert(lights.size() <= 8);
if (!lights.empty()) {
const LightEntry *ambient = lights[0];
assert(ambient->type == LightEntry::kAmbient);
_shader->setUniform("ambientColor", ambient->color);
}
Math::Matrix4 viewMatrix = StarkScene->getViewMatrix();
Math::Matrix3 viewMatrixRot = viewMatrix.getRotation();
for (uint i = 0; i < lights.size() - 1; i++) {
const LightEntry *l = lights[i + 1];
Math::Vector4d worldPosition;
worldPosition.x() = l->position.x();
worldPosition.y() = l->position.y();
worldPosition.z() = l->position.z();
worldPosition.w() = 1.0;
Math::Vector4d eyePosition = viewMatrix * worldPosition;
// The light type is stored in the w coordinate of the position to save an uniform slot
eyePosition.w() = l->type;
Math::Vector3d worldDirection = l->direction;
Math::Vector3d eyeDirection = viewMatrixRot * worldDirection;
_shader->setUniform(Common::String::format("lights[%d].position", i).c_str(), eyePosition);
_shader->setUniform(Common::String::format("lights[%d].direction", i).c_str(), eyeDirection);
_shader->setUniform(Common::String::format("lights[%d].color", i).c_str(), l->color);
Math::Vector4d params;
params.x() = l->falloffNear;
params.y() = l->falloffFar;
params.z() = l->innerConeAngle.getCosine();
params.w() = l->outerConeAngle.getCosine();
_shader->setUniform(Common::String::format("lights[%d].params", i).c_str(), params);
}
}
} // End of namespace Gfx
} // End of namespace Stark

View file

@ -26,6 +26,7 @@
#include "common/hashmap.h"
#include "engines/stark/hash-ptr.h"
#include "engines/stark/gfx/renderentry.h"
#include "engines/stark/visual/actor.h"
namespace Graphics {
@ -42,7 +43,7 @@ public:
OpenGLSActorRenderer(Driver *gfx);
virtual ~OpenGLSActorRenderer();
void render(const Math::Vector3d position, float direction) override;
void render(const Math::Vector3d position, float direction, const LightEntryArray &lights) override;
protected:
typedef Common::HashMap<FaceNode *, uint32> FaceBufferMap;
@ -59,6 +60,7 @@ protected:
uint32 createFaceEBO(const FaceNode *face);
void setBonePositionArrayUniform(const char *uniform);
void setBoneRotationArrayUniform(const char *uniform);
void setLightArrayUniform(const char *uniform, const LightEntryArray &lights);
};
} // End of namespace Gfx

View file

@ -42,7 +42,7 @@ RenderEntry::RenderEntry(Resources::ItemVisual *owner, const Common::String &nam
_clickable(true) {
}
void RenderEntry::render() {
void RenderEntry::render(const LightEntryArray &lights) {
if (!_visual) {
// warning("No visual for render entry '%s'", _name.c_str());
return;
@ -55,7 +55,7 @@ void RenderEntry::render() {
VisualActor *actor = _visual->get<VisualActor>();
if (actor) {
actor->render(_position3D, _direction3D);
actor->render(_position3D, _direction3D, lights);
}
VisualProp *prop = _visual->get<VisualProp>();

View file

@ -41,12 +41,32 @@ class ItemVisual;
namespace Gfx {
struct LightEntry {
enum Type {
kAmbient = 0,
kPoint = 1,
kDirectional = 2,
kSpot = 4
};
Type type;
Math::Vector3d color;
Math::Vector3d position;
Math::Vector3d direction;
Math::Angle innerConeAngle;
Math::Angle outerConeAngle;
float falloffNear;
float falloffFar;
};
typedef Common::Array<LightEntry *> LightEntryArray;
class RenderEntry {
public:
RenderEntry(Resources::ItemVisual *owner, const Common::String &name);
virtual ~RenderEntry() {}
void render();
void render(const LightEntryArray &lights);
void setVisual(Visual *visual);
void setPosition(const Common::Point &position);

View file

@ -23,8 +23,10 @@
#include "engines/stark/resources/layer.h"
#include "engines/stark/formats/xrc.h"
#include "engines/stark/resources/camera.h"
#include "engines/stark/resources/item.h"
#include "engines/stark/resources/light.h"
#include "common/debug.h"
@ -77,6 +79,17 @@ void Layer::enable(bool enabled) {
_enabled = enabled;
}
Gfx::LightEntryArray Layer::listLightEntries() {
Common::Array<Light *> lights = listChildren<Light>();
Gfx::LightEntryArray lightEntries;
for (uint i = 0; i < lights.size(); i++) {
lightEntries.push_back(lights[i]->getLightEntry());
}
return lightEntries;
}
Layer2D::~Layer2D() {
}

View file

@ -68,6 +68,9 @@ public:
/** Obtain the render entries for all items, including the background */
virtual Gfx::RenderEntryArray listRenderEntries() = 0;
/** Obtain a list of render entries for all the lights in the layer */
Gfx::LightEntryArray listLightEntries();
/** Scroll the layer to the specified position */
void setScrollPosition(const Common::Point &position);

View file

@ -22,12 +22,15 @@
#include "engines/stark/resources/light.h"
#include "engines/stark/gfx/renderentry.h"
#include "engines/stark/formats/xrc.h"
namespace Stark {
namespace Resources {
Light::~Light() {
delete _lightEntry;
}
Light::Light(Object *parent, byte subType, uint16 index, const Common::String &name) :
@ -35,7 +38,8 @@ Light::Light(Object *parent, byte subType, uint16 index, const Common::String &n
_outerConeAngle(0),
_innerConeAngle(0),
_falloffNear(100.0),
_falloffFar(500.0) {
_falloffFar(500.0),
_lightEntry(nullptr) {
_type = TYPE;
}
@ -52,6 +56,27 @@ void Light::readData(Formats::XRCReadStream *stream) {
}
}
void Light::onPostRead() {
Object::onPostRead();
_lightEntry = new Gfx::LightEntry();
_lightEntry->type = (Gfx::LightEntry::Type) _subType;
_lightEntry->direction = _direction;
_lightEntry->innerConeAngle = _innerConeAngle / 2.0;
_lightEntry->outerConeAngle = _outerConeAngle / 2.0;
_lightEntry->falloffNear = _falloffNear;
_lightEntry->falloffFar = _falloffFar;
// TODO: Add support for negative lights
}
Gfx::LightEntry *Light::getLightEntry() {
_lightEntry->color = _color;
_lightEntry->position = _position;
return _lightEntry;
}
void Light::printData() {
Common::Debug debug = streamDbg();
debug << "color: " << _color << "\n";

View file

@ -35,6 +35,10 @@ namespace Formats {
class XRCReadStream;
}
namespace Gfx {
class LightEntry;
}
namespace Resources {
/**
@ -44,18 +48,15 @@ class Light : public Object {
public:
static const Type::ResourceType TYPE = Type::kLight;
enum SubType {
kAmbiant = 0,
kPoint = 1,
kDirectional = 2,
kSpot = 4
};
Light(Object *parent, byte subType, uint16 index, const Common::String &name);
virtual ~Light();
// Resource API
void readData(Formats::XRCReadStream *stream) override;
void onPostRead() override;
/** Get the rendering object used to represent this light */
Gfx::LightEntry *getLightEntry();
protected:
void printData() override;
@ -67,6 +68,8 @@ protected:
float _outerConeAngle;
float _falloffNear;
float _falloffFar;
Gfx::LightEntry *_lightEntry;
};
} // End of namespace Resources

View file

@ -87,6 +87,19 @@ Gfx::RenderEntryArray Location::listRenderEntries() {
return renderEntries;
}
Gfx::LightEntryArray Location::listLightEntries() {
Gfx::LightEntryArray lightEntries;
for (uint i = 0; i < _layers.size(); i++) {
Layer *layer = _layers[i];
if (layer->isEnabled()) {
lightEntries.push_back(layer->listLightEntries());
}
}
return lightEntries;
}
void Location::initScroll(const Common::Point &maxScroll) {
_maxScroll = maxScroll;
_canScroll = _maxScroll.x != 0 || _maxScroll.y != 0;

View file

@ -62,6 +62,9 @@ public:
/** Obtain a list of render entries for all the items in the location */
Gfx::RenderEntryArray listRenderEntries();
/** Obtain a list of render entries for all the lights in the location */
Gfx::LightEntryArray listLightEntries();
/** Initialize scrolling from Camera data */
void initScroll(const Common::Point &maxScroll);

View file

@ -1,17 +1,16 @@
in vec2 Texcoord;
in vec3 Color;
OUTPUT
uniform bool textured;
uniform vec3 color;
uniform sampler2D tex;
void main()
{
if (textured)
{
outColor = texture(tex, Texcoord);
} else {
outColor = vec4(color, 1.0);
outColor = vec4(Color, 1.0);
if (textured) {
outColor *= texture(tex, Texcoord);
}
}

View file

@ -3,26 +3,116 @@ in vec3 position2;
in float bone1; // No ints as shader attributes in GLSL 1.20
in float bone2; // No ints as shader attributes in GLSL 1.20
in float boneWeight;
in vec3 normal;
in vec2 texcoord;
out vec2 Texcoord;
out vec3 Color;
uniform mat4 mvp;
uniform vec4 boneRotation[70];
uniform vec3 bonePosition[70];
struct Light {
vec4 position;
vec3 direction;
vec3 color;
vec4 params;
};
const int lightTypePoint = 1;
const int lightTypeDirectional = 2;
const int lightTypeSpot = 4;
const int maxLights = 8;
const int maxBones = 70;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
uniform vec4 boneRotation[maxBones];
uniform vec3 bonePosition[maxBones];
uniform Light lights[maxLights];
uniform vec3 ambientColor;
uniform vec3 color;
uniform bool textured;
vec4 eyePosition;
vec3 eyeNormal;
vec3 qrot(vec4 q, vec3 v)
{
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
}
vec3 pointLight(vec3 position, vec3 color, float falloffNear, float falloffFar) {
vec3 vertexToLight = position - eyePosition.xyz;
float dist = length(vertexToLight);
float attn = clamp((falloffFar - dist) / max(0.001, falloffFar - falloffNear), 0.0, 1.0);
vertexToLight = normalize(vertexToLight);
float incidence = max(0.0, dot(eyeNormal, vertexToLight));
return color * attn * incidence;
}
vec3 directionalLight(vec3 direction, vec3 color) {
float incidence = max(0.0, dot(eyeNormal, -direction));
return color * incidence;
}
vec3 spotLight(vec3 position, vec3 color, float falloffNear, float falloffFar, vec3 direction, float cosInnerAngle, float cosOuterAngle) {
vec3 vertexToLight = position - eyePosition.xyz;
vertexToLight = normalize(vertexToLight);
float cosAngle = max(0.0, dot(vertexToLight, -direction));
float cone = clamp((cosAngle - cosInnerAngle) / max(0.001, cosOuterAngle - cosInnerAngle), 0.0, 1.0);
return pointLight(position, color, falloffNear, falloffFar) * cone;
}
void main()
{
Texcoord = texcoord;
// Compute the vertex position in eye-space
vec3 b1 = qrot(boneRotation[int(bone1)], position1) + bonePosition[int(bone1)];
vec3 b2 = qrot(boneRotation[int(bone2)], position2) + bonePosition[int(bone2)];
vec3 pos = mix(b2, b1, boneWeight);
vec3 modelPosition = mix(b2, b1, boneWeight);
eyePosition = modelViewMatrix * vec4(modelPosition.xyz, 1.0);
gl_Position = mvp * vec4(pos.x, pos.y, pos.z, 1.0);
// Compute the vertex normal in eye-space
vec3 n1 = qrot(boneRotation[int(bone1)], normal);
vec3 n2 = qrot(boneRotation[int(bone2)], normal);
vec3 modelNormal = normalize(mix(n2, n1, boneWeight));
eyeNormal = normalMatrix * modelNormal;
eyeNormal = normalize(eyeNormal);
// Compute the vertex position in screen-space
gl_Position = projectionMatrix * eyePosition;
// Set the initial vertex color
if (textured) {
Color = vec3(1.0);
} else {
Color = color;
}
// Shade the vertex color according to the lights
vec3 lightColor = ambientColor;
for (int i = 0; i < maxLights; i++) {
int type = int(lights[i].position.w);
switch (type) {
case lightTypePoint:
lightColor += pointLight(lights[i].position.xyz, lights[i].color, lights[i].params.x, lights[i].params.y);
break;
case lightTypeDirectional:
lightColor += directionalLight(lights[i].direction, lights[i].color);
break;
case lightTypeSpot:
lightColor += spotLight(lights[i].position.xyz, lights[i].color, lights[i].params.x, lights[i].params.y,
lights[i].direction, lights[i].params.z, lights[i].params.w);
break;
}
}
Color *= clamp(lightColor, 0.0, 1.0);
}

View file

@ -55,13 +55,15 @@ GameWindow::GameWindow(Gfx::Driver *gfx, Cursor *cursor, ActionMenu *actionMenu,
void GameWindow::onRender() {
// List the items to render
_renderEntries = StarkGlobal->getCurrent()->getLocation()->listRenderEntries();
Resources::Location *location = StarkGlobal->getCurrent()->getLocation();
_renderEntries = location->listRenderEntries();
Gfx::LightEntryArray lightEntries = location->listLightEntries();
// Render all the scene items
Gfx::RenderEntryArray::iterator element = _renderEntries.begin();
while (element != _renderEntries.end()) {
// Draw the current element
(*element)->render();
(*element)->render(lightEntries);
// Go for the next one
element++;

View file

@ -23,16 +23,20 @@
#ifndef STARK_VISUAL_ACTOR_H
#define STARK_VISUAL_ACTOR_H
#include "engines/stark/visual/visual.h"
#include "common/array.h"
#include "math/matrix4.h"
#include "math/ray.h"
#include "math/vector3d.h"
#include "engines/stark/visual/visual.h"
namespace Stark {
namespace Gfx {
class TextureSet;
class LightEntry;
}
class Model;
@ -53,7 +57,7 @@ public:
void setTime(uint32 time);
bool intersectRay(const Math::Ray &ray, const Math::Vector3d position, float direction);
virtual void render(const Math::Vector3d position, float direction) = 0;
virtual void render(const Math::Vector3d position, float direction, const Common::Array<Gfx::LightEntry *> &lights) = 0;
protected:
Model *_model;