Fixing particles

This commit is contained in:
Vladislav Khorev 2026-03-08 19:07:02 +03:00
parent 9da3cc4401
commit 59b5bea540
11 changed files with 182 additions and 123 deletions

View File

@ -5,7 +5,7 @@
"texture": "resources/spark_white.png",
"speedRange": [10.0, 30.0],
"zSpeedRange": [-1.0, 1.0],
"scaleRange": [0.5, 1.0],
"scaleRange": [5.0, 10.0],
"lifeTimeRange": [200.0, 800.0],
"emissionRate": 50.0,
"maxParticles": 5,

View File

@ -1,20 +1,14 @@
{
"emissionRate": 100,
"maxParticles": 200,
"emissionRate": 0.4,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,
"emissionPoints": [
{
"position": [-1.0, 1.4, -3.5]
},
{
"position": [1.0, 1.4, -3.5]
}
],
"speedRange": [0.5, 2.0],
"zSpeedRange": [1.0, 3.0],
"scaleRange": [0.8, 1.2],
"lifeTimeRange": [600.0, 1400.0],
"lifeTimeRange": [300.0, 500.0],
"texture": "resources/spark.png",
"shaderProgramName": "spark"
}

View File

@ -1,6 +1,6 @@
{
"emissionRate": 10.0,
"maxParticles": 200,
"emissionRate": 0.4,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,
"emissionPoints": [

View File

@ -334,8 +334,10 @@ namespace ZL
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//processTickCount();
drawScene();
processTickCount();
//std::this_thread::sleep_for(std::chrono::milliseconds(50));
SDL_GL_SwapWindow(ZL::Environment::window);
}
@ -390,19 +392,16 @@ namespace ZL
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
//std::cout << "Finger down: id=" << event.tfinger.fingerId << " x=" << mx << " y=" << my << std::endl;
}
else if (event.type == SDL_FINGERUP) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
//std::cout << "Finger up: id=" << event.tfinger.fingerId << " x=" << mx << " y=" << my << std::endl;
}
else if (event.type == SDL_FINGERMOTION) {
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
//std::cout << "Finger motion: id=" << event.tfinger.fingerId << " x=" << mx << " y=" << my << std::endl;
}
#endif

View File

@ -506,9 +506,11 @@ namespace ZL
renderer.DrawVertexRenderStruct(spaceship);
}
renderer.PushMatrix();
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
renderer.TranslateMatrix(- /*0.5 * */ Environment::shipState.position);
std::cout << "Ship pos draw: " << Environment::shipState.position.transpose() << "\n";
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
@ -541,21 +543,6 @@ namespace ZL
renderer.shaderManager.PopShader();
/*
if (shipAlive) {
renderer.PushMatrix();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
renderer.PopMatrix();
}*/
if (showExplosion) {
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
}
@ -604,6 +591,17 @@ namespace ZL
glViewport(0, 0, Environment::width, Environment::height);
// Готовим данные всех эмиттеров (CPU + VBO upload) до начала отрисовки,
// чтобы draw() делал только GPU-вызовы без пауз между кораблём и частицами.
sparkEmitter.prepareForDraw(true);
sparkEmitterCargo.prepareForDraw(true);
explosionEmitter.prepareForDraw(false);
for (const auto& p : projectiles) {
if (p && p->isActive()) {
p->projectileEmitter.prepareForDraw(true);
}
}
CheckGlError();
float skyPercent = 0.0;
@ -1437,35 +1435,6 @@ namespace ZL
auto now_ms = newTickCount;
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -3.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -3.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
else
{
sparkEmitterPtr = &sparkEmitter;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{-1.0, 1.4-1.0, -3.5 + 16.0};
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{1.0, 1.4 - 1.0, -3.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
if (Environment::shipState.velocity > 0.1f)
{
sparkEmitterPtr->setIsActive(true);
}
else
{
sparkEmitterPtr->setIsActive(false);
}
sparkEmitterPtr->update(static_cast<float>(delta));
if (firePressed)
{
firePressed = false;
@ -1576,6 +1545,41 @@ namespace ZL
}
//--------------
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -6.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -6.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
else
{
sparkEmitterPtr = &sparkEmitter;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ -0.9, 1.4 - 1.0, -8.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.9, 1.4 - 1.0, -8.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
//sparkEmitterPtr->setEmissionPoints({ /*0.5* */Environment::shipState.position });
//sparkEmitterPtr->setEmissionPoints({ Vector3f(0, 0, 0) });
std::cout << "Ship pos empo: " << Environment::shipState.position.transpose() << "\n";
}
if (Environment::shipState.velocity > 0.1f)
{
sparkEmitterPtr->setIsActive(true);
}
else
{
sparkEmitterPtr->setIsActive(false);
}
sparkEmitterPtr->update(static_cast<float>(delta));
auto latestRemotePlayers = networkClient->getRemotePlayers();
std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) };
@ -1607,7 +1611,7 @@ namespace ZL
for (const auto& p : projectiles) {
if (p && p->isActive()) {
Vector3f worldPos = p->getPosition();
p->projectileEmitter.setEmissionPoints({ worldPos });
p->projectileEmitter.resetEmissionPoints({ worldPos });
p->projectileEmitter.update(static_cast<float>(delta));
}
}

View File

@ -168,6 +168,18 @@ namespace ZL {
drawDataDirty = false;
}
void SparkEmitter::prepareForDraw(bool withRotation) {
if (!configured) return;
prepareDrawData(withRotation);
if (!drawPositions.empty()) {
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
}
}
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight)
{
draw(renderer, zoom, screenWidth, screenHeight, true);
@ -186,35 +198,28 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 2!");
}
prepareDrawData(withRotation);
//prepareDrawData(withRotation);
if (drawPositions.empty()) {
return;
}
/*
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
*/
renderer.shaderManager.PushShader(shaderProgramName);
renderer.RenderUniform1i(textureUniformName, 0);
//float aspectRatio = static_cast<float>(screenWidth) / static_cast<float>(screenHeight);
//renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.SetMatrix();
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.PushMatrix();
//renderer.LoadIdentity();
//renderer.TranslateMatrix({ 0, 0, -1.0f * zoom });
//renderer.PushMatrix();
renderer.DrawVertexRenderStruct(sparkQuad);
renderer.PopMatrix();
//renderer.PopProjectionMatrix();
//renderer.PopMatrix();
glDisable(GL_BLEND);
renderer.shaderManager.PopShader();
@ -229,9 +234,19 @@ namespace ZL {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count();
if (isActive && elapsed >= emissionRate) {
emit();
lastEmissionTime = currentTime;
if (isActive && elapsed >= static_cast<long long>(emissionRate)) {
int emitCount = static_cast<int>(elapsed / emissionRate);
float elapsedF = static_cast<float>(elapsed);
for (int e = 0; e < emitCount; ++e) {
// e=0 — самая старая эмиссия, e=emitCount-1 — самая свежая
float ageMs = static_cast<float>(emitCount - 1 - e) * emissionRate;
// lerpT=0 → текущая позиция (новейшая), lerpT=1 → предыдущая (самая старая)
float lerpT = (elapsedF > 0.0f) ? min(ageMs / elapsedF, 1.0f) : 0.0f;
emit(ageMs, lerpT);
}
lastEmissionTime += std::chrono::milliseconds(
static_cast<long long>(emitCount * emissionRate));
drawDataDirty = true;
}
@ -270,7 +285,7 @@ namespace ZL {
}
}
void SparkEmitter::emit() {
void SparkEmitter::emit(float ageMs, float lerpT) {
if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 4!");
}
@ -278,7 +293,30 @@ namespace ZL {
if (emissionPoints.empty()) return;
bool emitted = false;
for (int i = 0; i < emissionPoints.size(); ++i) {
auto applyAge = [](SparkParticle& particle, float age) {
if (age <= 0.0f) return;
particle.position(0) += particle.velocity(0) * age / 1000.0f;
particle.position(1) += particle.velocity(1) * age / 1000.0f;
particle.position(2) += particle.velocity(2) * age / 1000.0f;
particle.lifeTime = age;
if (particle.lifeTime >= particle.maxLifeTime) {
particle.active = false;
} else {
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
particle.scale = 1.0f - lifeRatio * 0.8f;
}
};
// Вычисляем стартовую позицию с интерполяцией между prev и current
// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
bool canInterp = (prevEmissionPoints.size() == emissionPoints.size());
for (int i = 0; i < (int)emissionPoints.size(); ++i) {
Vector3f birthPos = emissionPoints[i];
if (canInterp && lerpT > 0.0f) {
birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT;
}
bool particleFound = false;
for (auto& particle : particles) {
@ -286,8 +324,9 @@ namespace ZL {
initParticle(particle, i);
particle.active = true;
particle.lifeTime = 0;
particle.position = emissionPoints[i];
particle.position = birthPos;
particle.emitterIndex = i;
applyAge(particle, ageMs);
particleFound = true;
emitted = true;
break;
@ -296,11 +335,11 @@ namespace ZL {
if (!particleFound && !particles.empty()) {
size_t oldestIndex = 0;
float maxLifeTime = 0;
float maxLifeRatio = 0;
for (size_t j = 0; j < particles.size(); ++j) {
if (particles[j].lifeTime > maxLifeTime) {
maxLifeTime = particles[j].lifeTime;
if (particles[j].lifeTime > maxLifeRatio) {
maxLifeRatio = particles[j].lifeTime;
oldestIndex = j;
}
}
@ -308,8 +347,9 @@ namespace ZL {
initParticle(particles[oldestIndex], i);
particles[oldestIndex].active = true;
particles[oldestIndex].lifeTime = 0;
particles[oldestIndex].position = emissionPoints[i];
particles[oldestIndex].position = birthPos;
particles[oldestIndex].emitterIndex = i;
applyAge(particles[oldestIndex], ageMs);
emitted = true;
}
}
@ -320,6 +360,14 @@ namespace ZL {
}
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
prevEmissionPoints = emissionPoints;
emissionPoints = positions;
drawDataDirty = true;
}
void SparkEmitter::resetEmissionPoints(const std::vector<Vector3f>& positions)
{
prevEmissionPoints.clear();
emissionPoints = positions;
drawDataDirty = true;
}
@ -487,8 +535,9 @@ namespace ZL {
std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
}
else {
std::cerr << "Emission points parsed but empty" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 10!");
setEmissionPoints({});
std::cout << "Emission points parsed but empty" << std::endl;
//throw std::runtime_error("Failed to load spark emitter config file 10!");
}
}
else {
@ -575,25 +624,6 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 18!");
}
std::cout << "Working with shaders 2" << std::endl;
/*
if (j.contains("vertexShader") && j.contains("fragmentShader")
&& j["vertexShader"].is_string() && j["fragmentShader"].is_string()) {
std::string v = j["vertexShader"].get<std::string>();
std::string f = j["fragmentShader"].get<std::string>();
std::cout << "Loading shaders - vertex: " << v << ", fragment: " << f << std::endl;
try {
renderer.shaderManager.AddShaderFromFiles(shaderProgramName, v, f, zipFile.c_str());
std::cout << "Shaders loaded successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Shader load error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 19!");
}
}*/
drawDataDirty = true;
configured = true;
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;

View File

@ -26,6 +26,7 @@ namespace ZL {
private:
std::vector<SparkParticle> particles;
std::vector<Vector3f> emissionPoints;
std::vector<Vector3f> prevEmissionPoints;
std::chrono::steady_clock::time_point lastEmissionTime;
float emissionRate;
bool isActive;
@ -62,6 +63,7 @@ namespace ZL {
float rate = 100.0f);
void setEmissionPoints(const std::vector<Vector3f>& positions);
void resetEmissionPoints(const std::vector<Vector3f>& positions);
void setTexture(std::shared_ptr<Texture> tex);
void setEmissionRate(float rate) { emissionRate = rate; }
void setShaderProgramName(const std::string& name) { shaderProgramName = name; }
@ -73,7 +75,10 @@ namespace ZL {
bool loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
void update(float deltaTimeMs);
void emit();
void emit(float ageMs = 0.0f, float lerpT = 0.0f);
// Вызывать ДО draw() в начале кадра: готовит данные и загружает в VBO.
void prepareForDraw(bool withRotation = true);
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight);
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation);

View File

@ -381,7 +381,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, positionVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.PositionData.size() * 12, &data.PositionData[0], GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, data.PositionData.size() * 12, &data.PositionData[0], GL_DYNAMIC_DRAW);
if (data.TexCoordData.size() > 0)
{
@ -392,7 +392,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, texCoordVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.TexCoordData.size() * 8, &data.TexCoordData[0], GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, data.TexCoordData.size() * 8, &data.TexCoordData[0], GL_DYNAMIC_DRAW);
}
if (data.NormalData.size() > 0)
@ -404,7 +404,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, normalVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.NormalData.size() * 12, &data.NormalData[0], GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, data.NormalData.size() * 12, &data.NormalData[0], GL_DYNAMIC_DRAW);
}
if (data.TangentData.size() > 0)
@ -416,7 +416,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, tangentVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.TangentData.size() * 12, &data.TangentData[0], GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, data.TangentData.size() * 12, &data.TangentData[0], GL_DYNAMIC_DRAW);
}
if (data.BinormalData.size() > 0)
@ -428,7 +428,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, binormalVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.BinormalData.size() * 12, &data.BinormalData[0], GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, data.BinormalData.size() * 12, &data.BinormalData[0], GL_DYNAMIC_DRAW);
}
if (data.ColorData.size() > 0)
@ -440,7 +440,7 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, colorVBO->getBuffer());
glBufferData(GL_ARRAY_BUFFER, data.ColorData.size() * 12, &data.ColorData[0], GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, data.ColorData.size() * 12, &data.ColorData[0], GL_DYNAMIC_DRAW);
}
}
@ -846,8 +846,25 @@ namespace ZL {
glVertexAttribPointer(shader->attribList[attribName], 3, GL_FLOAT, GL_FALSE, stride, pointer);
}
void Renderer::DisableVertexAttribArray(const std::string& attribName)
{
auto shader = shaderManager.GetCurrentShader();
auto it = shader->attribList.find(attribName);
if (it != shader->attribList.end())
glDisableVertexAttribArray(it->second);
}
void Renderer::DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct)
{
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
if (VertexRenderStruct.vao) {
glBindVertexArray(VertexRenderStruct.vao->getBuffer());
shaderManager.EnableVertexAttribArrays();
}
#endif
#endif
static const std::string vNormal("vNormal");
static const std::string vTangent("vTangent");
static const std::string vBinormal("vBinormal");
@ -861,51 +878,46 @@ namespace ZL {
{
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer());
VertexAttribPointer3fv(vNormal, 0, NULL);
//EnableVertexAttribArray(vNormal);
}
else
{
//DisableVertexAttribArray(vNormal);
DisableVertexAttribArray(vNormal);
}
if (VertexRenderStruct.data.TangentData.size() > 0)
{
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer());
VertexAttribPointer3fv(vTangent, 0, NULL);
//EnableVertexAttribArray(vTangent);
}
else
{
//DisableVertexAttribArray(vTangent);
DisableVertexAttribArray(vTangent);
}
if (VertexRenderStruct.data.BinormalData.size() > 0)
{
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer());
VertexAttribPointer3fv(vBinormal, 0, NULL);
//EnableVertexAttribArray(vBinormal);
}
else
{
//DisableVertexAttribArray(vBinormal);
DisableVertexAttribArray(vBinormal);
}
if (VertexRenderStruct.data.ColorData.size() > 0)
{
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer());
VertexAttribPointer3fv(vColor, 0, NULL);
//EnableVertexAttribArray(vColor);
}
else
{
//DisableVertexAttribArray(vColor);
DisableVertexAttribArray(vColor);
}
if (VertexRenderStruct.data.TexCoordData.size() > 0)
{
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer());
VertexAttribPointer2fv(vTexCoord, 0, NULL);
//EnableVertexAttribArray(vTexCoord);
}
else
{
//DisableVertexAttribArray(vTexCoord);
DisableVertexAttribArray(vTexCoord);
}
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());

View File

@ -138,6 +138,8 @@ namespace ZL {
void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer);
void DisableVertexAttribArray(const std::string& attribName);
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
};

View File

@ -200,7 +200,6 @@ namespace ZL {
{
glEnableVertexAttribArray(attrib.second);
}
}
@ -232,6 +231,19 @@ namespace ZL {
}
}
void ShaderManager::EnableVertexAttribArrays()
{
if (shaderStack.size() != 0) {
auto& shaderResource = shaderResourceMap[shaderStack.top()];
auto& shaderAttribList = shaderResource->attribList;
for (auto& attrib : shaderAttribList)
{
glEnableVertexAttribArray(attrib.second);
}
}
}
std::shared_ptr <ShaderResource> ShaderManager::GetCurrentShader() {
if (shaderStack.size() == 0) {
throw std::runtime_error("Shader stack underflow!");

View File

@ -42,6 +42,7 @@ namespace ZL {
void PushShader(const std::string& shaderName);
void PopShader();
void EnableVertexAttribArrays();
std::shared_ptr<ShaderResource> GetCurrentShader();
};