Added korean location

This commit is contained in:
Vladislav Khorev committed 2026-10-25 01:29:38 +06:00
1 parent d5ef126264
commit 96e748715b
80 files changed
+18949 -80

No files matched your search

+17 -1
View File
@@ -243,7 +243,15 @@ namespace FRG
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning_web.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow_web.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow_web.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE);
// Instancing variants of the static-object shaders (same sources + INSTANCED define)
renderer.shaderManager.AddShaderFromFiles("fog_inst", "resources/shaders/fog.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("darklands_fog_inst", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("shadow_depth_inst", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("fog_shadow_inst", "resources/shaders/fog_shadow_web.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("night_fog_inst", "resources/shaders/night_fog_web.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow_inst", "resources/shaders/night_fog_shadow_web.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("text2d",
"resources/shaders/text2d.vertex",
"resources/shaders/text2d_web.fragment",
@@ -303,6 +311,14 @@ namespace FRG
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE);
// Instancing variants of the static-object shaders (same sources + INSTANCED define)
renderer.shaderManager.AddShaderFromFiles("fog_inst", "resources/shaders/fog.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("darklands_fog_inst", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("shadow_depth_inst", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("fog_shadow_inst", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("night_fog_inst", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow_inst", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE, "#define INSTANCED\n");
renderer.shaderManager.AddShaderFromFiles("text2d",
"resources/shaders/text2d.vertex",
"resources/shaders/text2d_desktop.fragment",
+68 -1
View File
@@ -106,7 +106,11 @@ namespace FRG
{
// Load static game objects
gameObjects = GameObjectLoader::loadAndCreateGameObjects(params.gameObjectsJsonPath, renderer, CONST_ZIP_FILE);
{
StaticObjectSet staticObjects = GameObjectLoader::loadAndCreateGameObjects(params.gameObjectsJsonPath, renderer, CONST_ZIP_FILE);
gameObjects = std::move(staticObjects.single);
instancedBatches = std::move(staticObjects.batches);
}
SDL_PumpEvents();
// Load interactive objects
@@ -683,6 +687,27 @@ namespace FRG
}
void Location::drawInstancedStatics(const std::string& instancedShaderName, InstancedTexture textureKind,
const std::function<void()>& setUniforms)
{
if (instancedBatches.empty()) return;
renderer.shaderManager.PushShader(instancedShaderName);
if (setUniforms) setUniforms();
renderer.SetMatrix();
for (auto& batch : instancedBatches) {
if (textureKind != InstancedTexture::None) {
const auto& texture = (textureKind == InstancedTexture::Darklands) ? batch.textureDarklands : batch.texture;
if (!texture) continue;
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
}
renderer.DrawInstanced(batch);
}
renderer.shaderManager.PopShader();
}
void Location::drawGame()
{
@@ -718,6 +743,11 @@ namespace FRG
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
drawInstancedStatics("fog_inst", InstancedTexture::Color, [&]() {
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePos);
renderer.RenderUniform1f("uAlpha", 1.0f);
});
for (auto& intObj : interactiveObjects) {
if (intObj.state.isActive) {
@@ -812,6 +842,7 @@ namespace FRG
for (auto& [name, gameObj] : gameObjects) {
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
drawInstancedStatics("shadow_depth_inst", InstancedTexture::None);
for (auto& intObj : interactiveObjects) {
if (intObj.state.castShadow && intObj.loadedObject.texture) {
@@ -907,6 +938,16 @@ namespace FRG
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
#ifndef DEBUG_LIGHT
drawInstancedStatics("fog_shadow_inst", InstancedTexture::Color, [&]() {
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform1i("uShadowMap", 1);
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePos);
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
renderer.RenderUniform1f("uAlpha", 1.0f);
});
#endif
CheckGlError(__FILE__, __LINE__);
for (auto& intObj : interactiveObjects) {
@@ -1004,6 +1045,11 @@ namespace FRG
glBindTexture(GL_TEXTURE_2D, gameObj.textureDarklands->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
drawInstancedStatics("darklands_fog_inst", InstancedTexture::Darklands, [&]() {
renderer.RenderUniform1i(textureUniformName, 0);
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePos);
renderer.RenderUniform1f("uAlpha", 1.0f);
});
for (auto& intObj : interactiveObjects) {
if (intObj.state.isActive) {
@@ -1067,6 +1113,7 @@ namespace FRG
for (auto& [name, gameObj] : gameObjects) {
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
drawInstancedStatics("shadow_depth_inst", InstancedTexture::None);
for (auto& intObj : interactiveObjects) {
if (intObj.state.castShadowNight && intObj.loadedObject.texture) {
renderer.PushMatrix();
@@ -1279,6 +1326,26 @@ namespace FRG
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
renderer.DrawVertexRenderStruct(gameObj.mesh);
}
drawInstancedStatics(hasShadows ? "night_fog_shadow_inst" : "night_fog_inst", InstancedTexture::Color, [&]() {
renderer.RenderUniform1i(textureUniformName, 0);
if (hasShadows) {
renderer.RenderUniform1i("uShadowMap", 1);
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
renderer.RenderUniform3fv("uLightDir", plDirEye);
}
renderer.RenderUniform3fv("uPlayerEyePos", playerEyePos);
renderer.RenderUniform3fv("uAmbientColor", ambientColor);
renderer.RenderUniform3fv("uFogColor", fogColor);
renderer.RenderUniform1i("uPointLightCount", lightCount);
if (lightCount > 0) {
renderer.RenderUniform3fvArray("uPointLightPos[0]", lightCount, plPosEye);
renderer.RenderUniform3fvArray("uPointLightDir[0]", lightCount, plDirEye);
renderer.RenderUniform3fvArray("uPointLightColor[0]", lightCount, plColors);
renderer.RenderUniform2fvArray("uPointLightWorldXZ[0]", lightCount, plWorldXZ);
renderer.RenderUniform2fvArray("uPointLightLimitXZ[0]", lightCount, plLimitXZ);
}
renderer.RenderUniform1f("uAlpha", 1.0f);
});
for (auto& intObj : interactiveObjects) {
if (intObj.state.isActive) {
+11
View File
@@ -77,6 +77,9 @@ namespace FRG
std::unordered_map<std::string, LoadedGameObject> gameObjects;
// Repeated static objects (same mesh + textures) drawn with one instanced call each.
std::vector<InstancedBatch> instancedBatches;
std::vector<InteractiveObject> interactiveObjects;
// Shared: the same player moves from location to location and outlives them.
@@ -207,6 +210,14 @@ namespace FRG
int findInteractiveObjectIndex(const InteractiveObject* obj) const;
int findTeleportZoneIndex(const TeleportZone* tz) const;
enum class InstancedTexture { None, Color, Darklands };
// Draws instancedBatches with the instancing variant of the current pass shader
// (e.g. "fog_inst"). The variant is a separate program, so setUniforms must re-send
// the pass uniforms; the matrix stack tops are uploaded here.
void drawInstancedStatics(const std::string& instancedShaderName, InstancedTexture textureKind,
const std::function<void()>& setUniforms = {});
void resolveCharacterCollisions();
void updateDynamicReplans(int64_t deltaMs);
void loadTeleportZones(const std::string& jsonPath, const char* zipFile);
+102 -31
View File
@@ -55,6 +55,7 @@ namespace FRG {
data.positionY = item.value("positionY", 0.0f);
data.positionZ = item.value("positionZ", 0.0f);
data.scale = item.value("scale", 1.0f);
data.instanced = item.value("instanced", true);
return data;
}
@@ -154,6 +155,36 @@ namespace FRG {
return st;
}
static VertexDataStruct loadMeshData(const std::string& meshPath, const std::string& zipPath)
{
if (meshPath.size() > 4 && meshPath.compare(meshPath.size() - 4, 4, ".bin") == 0)
return LoadModelFromBinFile(meshPath, zipPath);
return LoadFromTextFile02(meshPath, zipPath);
}
static Eigen::Matrix3f makeRotationMatrix(const GameObjectData& data)
{
constexpr float kDeg2Rad = static_cast<float>(M_PI) / 180.0f;
Eigen::Quaternionf rot = Eigen::Quaternionf::Identity();
if (data.rotationX != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX * kDeg2Rad, Eigen::Vector3f::UnitX())) * rot;
if (data.rotationY != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY * kDeg2Rad, Eigen::Vector3f::UnitY())) * rot;
if (data.rotationZ != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ * kDeg2Rad, Eigen::Vector3f::UnitZ())) * rot;
return rot.toRotationMatrix();
}
// Equivalent of what buildLoadedObject + Move() bake into the vertices:
// RotateByMatrix computes v^T * R (= R^T * v), then Scale, then Move.
static Eigen::Matrix4f makeInstanceMatrix(const GameObjectData& data)
{
Eigen::Matrix4f m = Eigen::Matrix4f::Identity();
m.block<3, 3>(0, 0) = data.scale * makeRotationMatrix(data).transpose();
m.block<3, 1>(0, 3) = Eigen::Vector3f(data.positionX, data.positionY, data.positionZ);
return m;
}
LoadedGameObject GameObjectLoader::buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath)
{
LoadedGameObject obj;
@@ -164,20 +195,9 @@ namespace FRG {
if (!data.textureDarkandsPath.empty())
obj.textureDarklands = renderer.textureManager.LoadFromPng(data.textureDarkandsPath, zipPath);
if (data.meshPath.size() > 4 && data.meshPath.compare(data.meshPath.size() - 4, 4, ".bin") == 0)
obj.mesh.data = LoadModelFromBinFile(data.meshPath, zipPath);
else
obj.mesh.data = LoadFromTextFile02(data.meshPath, zipPath);
obj.mesh.data = loadMeshData(data.meshPath, zipPath);
constexpr float kDeg2Rad = static_cast<float>(M_PI) / 180.0f;
Eigen::Quaternionf rot = Eigen::Quaternionf::Identity();
if (data.rotationX != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX * kDeg2Rad, Eigen::Vector3f::UnitX())) * rot;
if (data.rotationY != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY * kDeg2Rad, Eigen::Vector3f::UnitY())) * rot;
if (data.rotationZ != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ * kDeg2Rad, Eigen::Vector3f::UnitZ())) * rot;
obj.mesh.data.RotateByMatrix(rot.toRotationMatrix());
obj.mesh.data.RotateByMatrix(makeRotationMatrix(data));
if (data.scale != 1.0f)
obj.mesh.data.Scale(data.scale);
@@ -189,34 +209,85 @@ namespace FRG {
// Public API
// -----------------------------------------------------------------------
std::unordered_map<std::string, LoadedGameObject> GameObjectLoader::loadAndCreateGameObjects(
StaticObjectSet GameObjectLoader::loadAndCreateGameObjects(
const std::string& jsonPath,
Renderer& renderer,
const std::string& zipPath)
{
std::unordered_map<std::string, LoadedGameObject> gameObjects;
StaticObjectSet result;
for (const auto& data : loadFromJson(jsonPath, zipPath)) {
// Service the OS message queue between loads so the window doesn't
// get flagged as "not responding" during long location loads.
SDL_PumpEvents();
logger() << "Loading game object: " << data.name << std::endl;
try {
LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath);
std::vector<GameObjectData> allData = loadFromJson(jsonPath, zipPath);
if (data.positionX != 0.0f || data.positionY != 0.0f || data.positionZ != 0.0f)
obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ });
// Group objects sharing mesh + textures (in file order) so they can be drawn instanced.
std::vector<std::vector<const GameObjectData*>> groups;
std::unordered_map<std::string, size_t> groupIndexByKey;
obj.mesh.RefreshVBO();
gameObjects[data.name] = std::move(obj);
logger() << "Successfully loaded: " << data.name << std::endl;
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load '" << data.name << "': " << e.what() << std::endl;
for (const auto& data : allData) {
if (!data.instanced) {
groups.push_back({ &data });
continue;
}
std::string key = data.meshPath + '\n' + data.texturePath + '\n' + data.textureDarkandsPath;
auto it = groupIndexByKey.find(key);
if (it == groupIndexByKey.end()) {
groupIndexByKey[key] = groups.size();
groups.push_back({ &data });
} else {
groups[it->second].push_back(&data);
}
}
logger() << "Total game objects loaded: " << gameObjects.size() << std::endl;
return gameObjects;
for (const auto& group : groups) {
// Service the OS message queue between loads so the window doesn't
// get flagged as "not responding" during long location loads.
SDL_PumpEvents();
const GameObjectData& first = *group.front();
if (group.size() == 1) {
logger() << "Loading game object: " << first.name << std::endl;
try {
LoadedGameObject obj = buildLoadedObject(first, renderer, zipPath);
if (first.positionX != 0.0f || first.positionY != 0.0f || first.positionZ != 0.0f)
obj.mesh.data.Move({ first.positionX, first.positionY, first.positionZ });
obj.mesh.RefreshVBO();
result.single[first.name] = std::move(obj);
logger() << "Successfully loaded: " << first.name << std::endl;
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load '" << first.name << "': " << e.what() << std::endl;
}
continue;
}
logger() << "Loading instanced batch: " << first.meshPath << " x" << group.size() << std::endl;
try {
InstancedBatch batch;
batch.name = first.name;
batch.texture = renderer.textureManager.LoadFromPng(first.texturePath, zipPath);
if (!first.textureDarkandsPath.empty())
batch.textureDarklands = renderer.textureManager.LoadFromPng(first.textureDarkandsPath, zipPath);
// Mesh stays in local space; rotation, scale and position live in the instance matrices.
batch.mesh.data = loadMeshData(first.meshPath, zipPath);
batch.mesh.RefreshVBO();
batch.instances.reserve(group.size());
for (const GameObjectData* d : group)
batch.instances.push_back(makeInstanceMatrix(*d));
batch.RefreshInstanceVBO();
result.batches.push_back(std::move(batch));
logger() << "Successfully loaded instanced batch: " << first.meshPath << std::endl;
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load instanced batch '" << first.meshPath << "': " << e.what() << std::endl;
}
}
logger() << "Total game objects loaded: " << result.single.size() << " single, "
<< result.batches.size() << " instanced batches" << std::endl;
return result;
}
std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
+11 -1
View File
@@ -26,10 +26,20 @@ namespace FRG {
float positionY = 0.0f;
float positionZ = 0.0f;
float scale = 1.0f;
// Objects sharing mesh and textures are drawn with one instanced call.
// Set "instanced": false in the JSON to keep an object as a standalone mesh.
bool instanced = true;
LoadedGameObjectState toLoadedGameObjectState() const;
};
// Result of loading a static object list: unique objects keep their own mesh,
// repeated ones are merged into instanced batches.
struct StaticObjectSet {
std::unordered_map<std::string, LoadedGameObject> single;
std::vector<InstancedBatch> batches;
};
struct InteractiveObjectData {
GameObjectData base;
float interactionRadius = 2.0f;
@@ -86,7 +96,7 @@ namespace FRG {
class GameObjectLoader {
public:
static std::unordered_map<std::string, LoadedGameObject> loadAndCreateGameObjects(
static StaticObjectSet loadAndCreateGameObjects(
const std::string& jsonPath,
Renderer& renderer,
const std::string& zipPath = ""
+16 -4
View File
@@ -113,9 +113,12 @@ PFNGLBINDBUFFERBASEPROC glBindBufferBase = NULL;
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = NULL;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = NULL;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArray = NULL;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = NULL;
#endif
PFNGLDRAWARRAYSINSTANCEDPROC glDrawArraysInstanced = NULL;
PFNGLVERTEXATTRIBDIVISORPROC glVertexAttribDivisor = NULL;
#endif
namespace FRG {
@@ -308,11 +311,20 @@ namespace FRG {
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
glDeleteVertexArray = (PFNGLDELETEVERTEXARRAYSPROC)wglGetProcAddress("glBindVertexArray");
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)wglGetProcAddress("glDeleteVertexArrays");
if (glGenVertexArrays == NULL ||
glBindVertexArray == NULL ||
glDeleteVertexArray == NULL)
glDeleteVertexArrays == NULL)
{
ok = false;
}
glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC)wglGetProcAddress("glDrawArraysInstanced");
glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)wglGetProcAddress("glVertexAttribDivisor");
if (glDrawArraysInstanced == NULL ||
glVertexAttribDivisor == NULL)
{
ok = false;
}
+5 -1
View File
@@ -150,7 +150,11 @@ extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArray;
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
//Instancing (core since OpenGL 3.3)
extern PFNGLDRAWARRAYSINSTANCEDPROC glDrawArraysInstanced;
extern PFNGLVERTEXATTRIBDIVISORPROC glVertexAttribDivisor;
#else
#endif
+67 -2
View File
@@ -131,7 +131,7 @@ namespace FRG {
glDeleteVertexArrays(1, &vao);
#else
//Windows
glDeleteVertexArray(1, &vao);
glDeleteVertexArrays(1, &vao);
#endif
#endif
#endif
@@ -920,7 +920,73 @@ namespace FRG {
glDisableVertexAttribArray(it->second);
}
void InstancedBatch::RefreshInstanceVBO()
{
if (instances.empty())
{
return;
}
if (!instanceVBO)
{
instanceVBO = std::make_shared<VBOHolder>();
}
// Matrix4f is column-major with no padding: 16 floats per instance.
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, instances.size() * sizeof(Matrix4f), instances.data(), GL_STATIC_DRAW);
}
void Renderer::DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct)
{
BindVertexRenderStructAttribs(VertexRenderStruct);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
}
void Renderer::DrawInstanced(const InstancedBatch& batch)
{
if (batch.instances.empty() || !batch.instanceVBO || !batch.mesh.positionVBO)
{
return;
}
BindVertexRenderStructAttribs(batch.mesh);
static const char* const instanceAttribNames[4] = { "iModel0", "iModel1", "iModel2", "iModel3" };
auto shader = shaderManager.GetCurrentShader();
glBindBuffer(GL_ARRAY_BUFFER, batch.instanceVBO->getBuffer());
for (int i = 0; i < 4; i++)
{
auto it = shader->attribList.find(instanceAttribNames[i]);
if (it == shader->attribList.end())
{
continue;
}
glVertexAttribPointer(it->second, 4, GL_FLOAT, GL_FALSE, sizeof(Matrix4f),
reinterpret_cast<const void*>(static_cast<size_t>(i) * 4 * sizeof(float)));
glVertexAttribDivisor(it->second, 1);
}
glDrawArraysInstanced(GL_TRIANGLES, 0,
static_cast<GLsizei>(batch.mesh.data.PositionData.size()),
static_cast<GLsizei>(batch.instances.size()));
// The divisor is vertex array state: reset it so that later non-instanced draws
// with these attribute locations are not affected.
for (int i = 0; i < 4; i++)
{
auto it = shader->attribList.find(instanceAttribNames[i]);
if (it != shader->attribList.end())
{
glVertexAttribDivisor(it->second, 0);
}
}
}
void Renderer::BindVertexRenderStructAttribs(const VertexRenderStruct& VertexRenderStruct)
{
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
@@ -988,7 +1054,6 @@ namespace FRG {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
VertexAttribPointer3fv(vPosition, 0, NULL);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
}
void worldToScreenCoordinates(Vector3f objectPos,
+22
View File
@@ -87,6 +87,22 @@ namespace FRG {
void AssignFrom(const VertexDataStruct& v);
};
// One mesh drawn many times with a per-instance model matrix (glDrawArraysInstanced).
// The mesh is stored in local space; each instance matrix is fed to the *_inst shaders
// as four vec4 attributes (iModel0..iModel3, divisor 1).
struct InstancedBatch
{
std::string name;
VertexRenderStruct mesh;
std::shared_ptr<Texture> texture;
std::shared_ptr<Texture> textureDarklands;
std::vector<Matrix4f> instances;
std::shared_ptr<VBOHolder> instanceVBO;
void RefreshInstanceVBO();
};
VertexDataStruct CreateRect2D(Vector2f center, Vector2f halfWidthHeight, float zLevel);
VertexDataStruct CreatePolygonFloor(const std::vector<Eigen::Vector2f>& polygon, float yLevel, const Vector3f& color);
VertexDataStruct CreateRectHorizontalSections2D(Vector2f center, Vector2f halfWidthHeight, float zLevel, size_t sectionCount);
@@ -150,6 +166,12 @@ namespace FRG {
void DisableVertexAttribArray(const std::string& attribName);
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
// The current shader must be an instancing variant (compiled with INSTANCED).
void DrawInstanced(const InstancedBatch& batch);
protected:
void BindVertexRenderStructAttribs(const VertexRenderStruct& VertexRenderStruct);
};
+28
View File
@@ -160,10 +160,36 @@ namespace FRG {
}
static std::string insertDefinesAfterVersion(const std::string &source, const std::string &defines) {
if (defines.empty()) {
return source;
}
size_t versionPos = source.find("#version");
if (versionPos == std::string::npos) {
return defines + source;
}
size_t lineEnd = source.find('\n', versionPos);
if (lineEnd == std::string::npos) {
return source + "\n" + defines;
}
return source.substr(0, lineEnd + 1) + defines + source.substr(lineEnd + 1);
}
void ShaderManager::AddShaderFromFiles(const std::string &shaderName,
const std::string &vertexShaderFileName,
const std::string &fragmentShaderFileName,
const std::string &ZIPFileName) {
AddShaderFromFiles(shaderName, vertexShaderFileName, fragmentShaderFileName, ZIPFileName, "");
}
void ShaderManager::AddShaderFromFiles(const std::string &shaderName,
const std::string &vertexShaderFileName,
const std::string &fragmentShaderFileName,
const std::string &ZIPFileName,
const std::string &vertexDefines) {
#ifdef __ANDROID__
__android_log_print(ANDROID_LOG_INFO, "ShaderManager",
"Loading shader: %s", shaderName.c_str());
@@ -194,6 +220,8 @@ namespace FRG {
fragmentShader = readTextFile(fragmentShaderFileName);
}
vertexShader = insertDefinesAfterVersion(vertexShader, vertexDefines);
shaderResourceMap[shaderName] = std::make_shared<ShaderResource>(vertexShader,
fragmentShader);
}
+4
View File
@@ -38,6 +38,10 @@ namespace FRG {
public:
void AddShaderFromFiles(const std::string& shaderName, const std::string& vertexShaderFileName, const std::string& fragmentShaderFileName, const std::string& ZIPFileName = "");
// Same as above, but vertexDefines (e.g. "#define INSTANCED\n") is inserted into the
// vertex shader source right after its #version line (or at the very top if it has none).
void AddShaderFromFiles(const std::string& shaderName, const std::string& vertexShaderFileName, const std::string& fragmentShaderFileName, const std::string& ZIPFileName, const std::string& vertexDefines);
void PushShader(const std::string& shaderName);
void PopShader();
void EnableVertexAttribArrays();