My experience with shaders is minimal, my experience is SFML is a little more, with C++ a little more than that. Now combining them, and stumped after trying and chasing the issues I can think of. Help / Ideas are appreciated.
This text drawn to the graphics window confirms that shaders are available.
std::string debugText = "";
if ( sf::Shader::isAvailable() )
debugText = "Shaders are available on this system";
else
debugText = "No shaders, sorry";
This was my initial attempt, using an external GLSL file, and after drawing a base tile layer, loading subsequent layers (roots, rocks, grass) and drawing them to the render texture called terrainGrassLayer using that shader:
sf::Shader tileBlend;
bool shaderLoaded = tileBlend.loadFromFile( "src/TerrainTest.glsl", sf::Shader::Fragment );
...
if ( sf::Shader::isAvailable() && shaderLoaded )
{
// do alpha setting each tile for each terrain layer (potentially blending over texture) with GLSL
...
terrainGrassLayer.draw( terrainLayer, &tileBlend );
}
else
{
// tile blend shader not found, just draw tile with tile center alpha value
terrainLayer.setColor( pixelAlpha );
terrainGrassLayer.draw( terrainLayer );
// in other words, do far less interesting alpha assignment and draw to a render texture
}
...
// (then in main loop)
...
// draw calls
rWin.clear();
rWin.setView( vw );
rWin.draw( terrainLayerSprite );
...
With the above, I can confirm (again) Shaders are available, but that the shader did not load. I can confirm the shader did not load by commenting out the draw( terrainLayer ) line and seeing only a base terrain tile layer (dirt) drawn earlier on. So, only the else block works at this time, and shaderLoaded must be false.
I tried moving the filepath (project directory, cpp source directory, image asset directory in my project), tried re-writing the shader a couple times, including GLSL from SFML examples.
So, I tried loading from memory instead (figuring my GLSL, even the most basic I can make it) isn't right:
const std::string fragmentShader = \
"uniform sampler2D tSample;" \
"uniform float tileAlphaCenter;" \
"void main()" \
"{" \
" vec4 pixel = texture2d( tSample, gl_TexCoord[0].xy );" \
" pixel.a = tileAlphaCenter;" \
" gl_FragColor = gl_Color * pixel;" \
"}";
bool shaderLoaded = tileBlend.loadFromMemory( fragmentShader, sf::Shader::Fragment );
Still, shaderLoaded is false, and frustratingly, there is no standard error output. But, there should be, if the load didn't work, right? The idea that there is no SFML output, just warnings from the compiler regarding C++11 standards leads me to wonder if I'm looking for SFML errors in the wrong place in CodeBlocks. :\ Like, I say, I'm stumped and up for any ideas. 'Much appreciated.
'Need to see the GLSL file I tried?
uniform sampler2D tSample;
uniform float tileAlphaCenter;
void main()
{
vec4 pixel = texture2d( tSample, gl_TexCoord[0].xy );
pixel.a = tileAlphaCenter;
gl_FragColor = gl_Color * pixel;
}
I'll save the much fancier GLSL file I started with for another time. ;)
Thank you, yes, fixed. (the fancier original had a capital D there) 'Appreciated.
And, I agree, there should be a compilation error nestled in the build log output somewhere, but I'm not seeing it. Incidentally, another thing I tried was to insert my GLSL attempts into an online sandbox. SFML should output something if I just try and load a non-existent file or empty string, yeah?
// TEST FORCE COMPILATION ERROR MSG
sf::Shader dummy;
dummy.loadFromMemory("", sf::Shader::Fragment);
So, if this should show me an error, clearly, I'm not looking in the right place for it.
[EDIT:] ... and watching the shader variable, tracing via breakpoints, and watching the progress logged only confirms that, yes, the previous loading operations mentioned fail without mention in (all) console logs and, yes, the data in these is left uninitialized. :\
[EDIT: more complete version in next reply]
I understand. I tried to provide relevant code to the problem I've reduced it to: loadFromMemory and loadFromFile both return false.
If you think the problem is less about loadFromMemory or loadFromFile, I'm all ears.
I wish the problem were actually GLSL related, because I think I could work through that well enough. For example, if setting my float uniform wasn't working, and gave me a default zero value, I'd have a chance to see that with a hard-coded alpha value in GLSL for comparison.
Here's a little more complete version of the steps I'm taking after trying to load. (pls excuse breaking Demeter's law at this stage):
sf::Shader tileBlend;
//bool shaderLoaded = tileBlend.loadFromFile( "src/TerrainTest.glsl", sf::Shader::Fragment );
//bool shaderLoaded = tileBlend.loadFromFile( "src/TerrainTileBlend.glsl", sf::Shader::Fragment );
const std::string fragmentShader = \
"uniform sampler2D tSample;" \
"uniform float tileAlphaCenter;" \
"void main()" \
"{" \
" vec4 pixel = texture2D( tSample, gl_TexCoord[0].xy );" \
" pixel.a = tileAlphaCenter;" \
" gl_FragColor = gl_Color * pixel;" \
"}";
bool shaderLoaded = tileBlend.loadFromMemory( fragmentShader, sf::Shader::Fragment );
...
// selection of the next terrain layer texture to draw as ltexture
...
// getX and getY relate to pixel coordinates of a repeatable noise texture
pixelAlpha.a = texMgr.texMaskNoise.copyToImage().getPixel(getX, getY).a;
if ( sf::Shader::isAvailable() && shaderLoaded )
{
// blending across tile to surrounding eight mask pixel alpha values
// 1 2 3
// 4 X 5
// 6 7 8
// configure and utilize tileBlend shader
tileBlend.setUniform("tSample", lTexture);
pixelAlpha.a = texMgr.texMaskNoise.copyToImage().getPixel(getX, getY).a;
float centerA = (float)(pixelAlpha.a/255);
tileBlend.setUniform("tileAlphaCenter", centerA);
...
// (the fancier stuff mentioned in original post, linear interpolation between centerA and surrounding tile alpha values as found from noise texture, set those uniforms the same way)
terrainGrassLayer.draw( terrainLayer, &tileBlend );
}
else {
// tile blend shader not found, just draw tile with tile center alpha value
terrainLayer.setColor( pixelAlpha );
terrainGrassLayer.draw( terrainLayer );
}
...
if ( shaderLoaded )
debugText += "\n... and shader loaded!";
else
debugText += "\n... and shader not loaded";
'Hope that helps without dumping all the prototype code I have. It doesn't appear the things related to GLSL are even being used, because they're apparently not being loaded.
Again, it's the shaderLoaded bool that's indicating the problem. The lack of console feedback is another (related?) mystery.
I hope this is complete enough to review. It's not elegant of course:
#include <math.h>
#include <cmath>
#include <string>
#include <SFML/Graphics.hpp>
#include <SFML/Window/VideoMode.hpp>
#include <SFML/Audio.hpp>
// from headers --
sf::Font titleFont, headingFont, textFont;
bool FontInit() {
bool loadFontOkay = true;
if ( !titleFont.loadFromFile("image/fontTitle.ttf") ||
!headingFont.loadFromFile("image/fontHeading.ttf") ||
!textFont.loadFromFile("image/fontText.ttf") ) {
// error
loadFontOkay = false;
}
return loadFontOkay;
}
sf::IntRect texSize = sf::IntRect(0, 0, 32, 32);
class TextureManager {
public:
sf::Texture texMaskNoise;
TextureManager() { MaskTextureInit(); }
private:
void MaskTextureInit()
{
texMaskNoise.loadFromFile("image/Mask_Noise.png", texSize);
}
};
static TextureManager texMgr;
// -- end from headers
// C++, SFML, OpenGL
int main()
{
// rand seed
srand(time(NULL));
// main game setup //
// window
sf::RenderWindow rWin( sf::VideoMode( 1024, 576, 256 ), "X" );
sf::VideoMode vm( sf::VideoMode::getDesktopMode() );
rWin.setPosition( sf::Vector2i( (( vm.width / 2 ) - 512 ) , (( vm.height / 2 ) - 320 ) ) );
// time and space
sf::Clock frameTimer;
frameTimer.restart();
float timeDelta = 0.f;
float globalTime = 1.f;
float globalScale = 2.f;
// view port
sf::View vw;
vw.setSize(1024.f, 576.f);
vw.setViewport(sf::FloatRect(0.f, 0.f, 1.f, 1.f));
vw.setCenter(320.f, 320.f);
// terrain
// TODO: implement terrain manager
// holds all terrain textures + noise mask
// uses terrain layer offsets from custom Scene class
// handles render texture + tile blend shader
// scrolls with offset to draw tiles in view, based on player position, with updated render texture center
// 10x10 tiles = 320x320 pixels
// 32x32 tiles = 1024x1024 pixels
sf::Texture tTexture;
if ( !tTexture.loadFromFile("image/Terrain_Soil_Base.png", sf::IntRect(0,0,32,32) ) )
{
// error
}
tTexture.setRepeated(true);
tTexture.setSmooth(false);
sf::Sprite terrain;
terrain.setTexture(tTexture);
terrain.setTextureRect(sf::IntRect(0, 0, 32 * 10, 32 * 10));
terrain.setOrigin(160.f, 160.f);
terrain.setScale(globalScale, globalScale);
terrain.setPosition(320.f, 320.f);
// terrain layer prototype
sf::RenderTexture terrainGrassLayer;
sf::ContextSettings settings;
settings.depthBits = 8;
terrainGrassLayer.create( 32 * 10 * globalScale, 32 * 10 * globalScale, settings );
sf::Texture lTexture;
if ( !lTexture.loadFromFile("image/Terrain_Soil_Grass.png", sf::IntRect(0,0,32,32) ) )
{
// error
}
tTexture.setRepeated(true);
tTexture.setSmooth(false);
sf::Sprite terrainLayer;
terrainLayer.setTexture(lTexture);
terrainLayer.setTextureRect(sf::IntRect(0,0,32,32));
terrainLayer.setOrigin(160.f, 160.f);
terrainLayer.setScale(globalScale, globalScale);
terrainLayer.setPosition(320.f, 320.f);
terrainGrassLayer.draw( terrain );
// find all 1024 alpha values of the noise mask texture pixels, use them to set alpha of each layer 32x32 tile
// TODO: store these values instead of continually reading from image file
// attempt to load shader
// FIXME: shaders are available, but failing to load, either from src/ or image/
sf::Shader tileBlend;
//bool shaderLoaded = tileBlend.loadFromFile( "src/TerrainTest.glsl", sf::Shader::Fragment );
//bool shaderLoaded = tileBlend.loadFromFile( "src/TerrainTileBlend.glsl", sf::Shader::Fragment );
const std::string vertexShader = \
"void main()" \
"{" \
" // transform the vertex position" \
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;" \
"" \
" // transform the texture coordinates" \
" gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;" \
"" \
" // forward the vertex color" \
" gl_FrontColor = gl_Color;" \
"}";
const std::string fragmentShader = \
"uniform sampler2D tSample;" \
"uniform float tileAlphaCenter;" \
"void main()" \
"{" \
" vec4 pixel = texture2D( tSample, gl_TexCoord[0].xy );" \
" pixel.a = tileAlphaCenter;" \
" gl_FragColor = gl_Color * pixel;" \
"}";
//bool shaderLoaded = tileBlend.loadFromMemory( vertexShader, fragmentShader );
bool shaderLoaded = tileBlend.loadFromMemory( fragmentShader, sf::Shader::Fragment );
std::string debugText = "";
if ( sf::Shader::isAvailable() )
debugText += "Shaders are available on this system";
else
debugText = "No shaders, sorry";
/*
// TEST FORCE COMPILATION ERROR MSG
sf::Shader dummy;
if ( !dummy.loadFromMemory("", sf::Shader::Fragment) )
{
debugText += "\n... dummy fails ...";
}
*/
for ( int t=0; t<5; t++ )
{
int offsetX, offsetY;
offsetX = 0;
offsetY = 0;
switch (t)
{
case 0:
lTexture.loadFromFile("image/Terrain_Soil_Rock.png", sf::IntRect(0,0,32,32) );
break;
case 1:
lTexture.loadFromFile("image/Terrain_Soil_Root.png", sf::IntRect(0,0,32,32) );
break;
case 2:
lTexture.loadFromFile("image/Terrain_Soil_Pebble.png", sf::IntRect(0,0,32,32) );
break;
case 3:
lTexture.loadFromFile("image/Terrain_Soil_Grass.png", sf::IntRect(0,0,32,32) );
break;
case 4:
lTexture.loadFromFile("image/Terrain_Soil_Flower.png", sf::IntRect(0,0,32,32) );
break;
}
offsetX = (rand() % 32);
offsetY = (rand() % 32);
for ( int i=0; i<32; i++ )
{
for ( int n=0; n<32; n++ )
{
// TODO: migrate to function
// TODO: allow function to return alpha value from pixel 'wrapped around' texture edge
terrainLayer.setPosition( 320.f + (i * 32 * globalScale), 320.f + (n * 32 * globalScale) );
sf::Color pixelAlpha = sf::Color::White;
unsigned int getX, getY;
getX = i + offsetX;
getY = n + offsetY;
if ( getX > 31 )
getX -= 31;
if ( getY > 31 )
getY -= 31;
pixelAlpha.a = texMgr.texMaskNoise.copyToImage().getPixel(getX, getY).a;
if ( sf::Shader::isAvailable() && shaderLoaded )
{
// blending across tile to surrounding eight mask pixel alpha values
// 1 2 3
// 4 X 5
// 6 7 8
// configure and utilize tileBlend shader
tileBlend.setUniform("tSample", lTexture);
pixelAlpha.a = texMgr.texMaskNoise.copyToImage().getPixel(getX, getY).a;
float centerA = (float)(pixelAlpha.a/255);
tileBlend.setUniform("tileAlphaCenter", centerA);
float topleftA = 0.f;
if ( getX > 0 && getY > 0 )
{
topleftA = (float)(texMgr.texMaskNoise.copyToImage().getPixel((getX-1),(getY-1)).a / 255);
topleftA += centerA;
topleftA /= 2.f;
}
else
topleftA = centerA;
float topA = 0.f;
if ( getY > 0 )
{
topA = (float)(texMgr.texMaskNoise.copyToImage().getPixel(getX,(getY-1)).a / 255);
topA += centerA;
topA /= 2.f;
}
else
topA = centerA;
float toprightA = 0.f;
if ( getX < 31 && getY > 0 )
{
toprightA = (float)(texMgr.texMaskNoise.copyToImage().getPixel((getX+1),(getY-1)).a / 255);
toprightA += centerA;
toprightA /= 2.f;
}
else
toprightA = centerA;
float leftA = 0.f;
if ( getX > 0 )
{
leftA = (float)(texMgr.texMaskNoise.copyToImage().getPixel((getX-1),getY).a / 255);
leftA += centerA;
leftA /= 2.f;
}
else
leftA = centerA;
float rightA = 0.f;
if ( getX < 31 )
{
rightA = (float)(texMgr.texMaskNoise.copyToImage().getPixel((getX+1),getY).a / 255);
rightA += centerA;
rightA /= 2.f;
}
else
rightA = centerA;
float bottomleftA = 0.f;
if ( getX > 0 && getY < 31 )
{
bottomleftA = (float)(texMgr.texMaskNoise.copyToImage().getPixel((getX-1),(getY+1)).a / 255);
bottomleftA += centerA;
bottomleftA /= 2.f;
}
else
bottomleftA = centerA;
float bottomA = 0.f;
if ( getY < 31 )
{
bottomA = (float)(texMgr.texMaskNoise.copyToImage().getPixel(getX,(getY+1)).a / 255);
bottomA += centerA;
bottomA /= 2.f;
}
else
bottomA = centerA;
float bottomrightA = 0.f;
if ( getX < 31 && getY < 31 )
{
bottomrightA = (float)(texMgr.texMaskNoise.copyToImage().getPixel((getX+1),(getY+1)).a / 255);
bottomrightA += centerA;
bottomrightA /= 2.f;
}
else
bottomrightA = centerA;
tileBlend.setUniform("tileAlphaTopLeft", topleftA);
tileBlend.setUniform("tileAlphaTop", topA);
tileBlend.setUniform("tileAlphaTopRight", toprightA);
tileBlend.setUniform("tileAlphaLeft", leftA);
tileBlend.setUniform("tileAlphaRight", rightA);
tileBlend.setUniform("tileAlphaBottomLeft", bottomleftA);
tileBlend.setUniform("tileAlphaBottom", bottomA);
tileBlend.setUniform("tileAlphaBottomRight", bottomrightA);
terrainGrassLayer.draw( terrainLayer, &tileBlend );
}
else
{
// tile blend shader not found, just draw tile with tile center alpha value
terrainLayer.setColor( pixelAlpha );
terrainGrassLayer.draw( terrainLayer );
}
}
}
}
if ( shaderLoaded )
debugText += "\n... and shader loaded!";
else
debugText += "\n... and shader not loaded";
terrainGrassLayer.display();
sf::Sprite terrainLayerSprite;
terrainLayerSprite.setTexture( terrainGrassLayer.getTexture() );
// debug feedback
sf::Text debugLine;
debugLine.setFont(textFont);
debugLine.setScale(.381f,.381f);
debugLine.setPosition(32.f,48.f);
// main loop
rWin.setActive();
rWin.setFramerateLimit(60);
while ( rWin.isOpen() )
{
// time delta
timeDelta = frameTimer.restart().asSeconds() * globalTime;
// window events
sf::Event event;
while (rWin.pollEvent(event))
{
if (event.type == sf::Event::Closed)
rWin.close();
if (event.type == sf::Event::Resized) {
rWin.setSize( sf::Vector2u( 1024, 576 ) );
rWin.setPosition( sf::Vector2i( (( vm.width / 2 ) - 512 ) , (( vm.height / 2 ) - 320 ) ) );
}
}
// draw calls
rWin.clear();
rWin.setView( vw );
rWin.draw( terrainLayerSprite );
// debug
debugLine.setString(debugText);
if ( debugLine.getString() != "" )
rWin.draw(debugLine);
rWin.display();
}
}
Creating a separate version was a good idea. Compiled and got a loaded shader (from memory), and saw something I think you said.
pixelAlpha.a = texMgr.texMaskNoise.copyToImage().getPixel(getX, getY).a;
float centerA = (float)(pixelAlpha.a/255);
tileBlend.setUniform("tileAlphaCenter", centerA);
...wasn't going to work, and the results showed alpha clipping to either full or not.
Corrected to: pixelAlpha.a = texMgr.texMaskNoise.copyToImage().getPixel(getX, getY).a;
float centerA = (pixelAlpha.a/255.f);
tileBlend.setUniform("tileAlphaCenter", centerA);
So, I was able to migrate that fix throughout the fancy stuff, and fix at least one issue: passing floats that weren't floats. Okay, with the shader loaded in this version, I can now focus on what might be wrong loading GLSL from file. (first, I'll check paths again, then focus on the GLSL)