Divide two 64 bit uint mapped as two 32 bit uint to extract fraction as [0.0, 1.0] float - glsl

Due to limitations of GL ES 3.0 to max 32 Bit on my target devices I end up with two 64 uint mapped each as two 32 bit uint in a GLSL #version 300 es fragment shader. I now need to map the fraction of those two uint as a color from 0.0 to 1.0 for currentTime in range [startOfLife, endOfLife].
The problem here of course is that I can't use 64 bit uint as the extensions is not available on my devices.
What I have so far:
#version 300 es
precision highp float;
precision highp usampler2D;
precision highp int;
in vec2 v_texCoords;
uniform usampler2D u_metaTexture;
uniform int u_currentTimeHigh32Bit;
uniform int u_currentTimeLow32Bit;
out vec4 fragColor;
void main()
{
// Data encoded in a FBO texture
uvec4 metaData_raw = texture(u_metaTexture, v_texCoords);
uint endOfLifeHigh32Bit = metaData_raw.r;
uint endOfLifeLow32Bit = metaData_raw.g;
uint startOfLifeHigh32Bit = metaData_raw.b;
uint startOfLifeLow32Bit = metaData_raw.a;
uint currentTimeHigh32Bit = uint(u_currentTimeHigh32Bit);
uint currentTimeLow32Bit = uint(u_currentTimeLow32Bit);
// Discard pixel if end of life is earlier than current currenttime
if ((currentTimeHigh32Bit > endOfLifeHigh32Bit
|| (currentTimeHigh32Bit == endOfLifeHigh32Bit && currentTimeLow32Bit > endOfLifeLow32Bit))) {
discard; // Already dead
}
// Here we know that currentTime < endOfLife
uint lifeTimeHigh32Bit = endOfLifeHigh32Bit - startOfLifeHigh32Bit;
uint lifeTimeLow32Bit = endOfLifeLow32Bit - startOfLifeLow32Bit;
uint remainingTimeHigh32Bit = endOfLifeHigh32Bit - currentTimeHigh32Bit;
uint remainingTimeLow32Bit = endOfLifeLow32Bit - currentTimeLow32Bit;
// Alpha should be 0.0 for currentTime <= startTime
// and ]0.0, 1.0] for startTime < currentTime < endOfLife
// and is already discared above for when endOfLife < currenttime
float alpha = 1.0 - /* remainingTime / lifeTime */;
fragColor = vec4(1, 1, 1, alpha);
}
Any idea?

Related

'variable' : is not available in current GLSL version gl_TexCoord

I have coded a fragment shader in vizard IDE and its not working. The code is free of compilation errors except for one which says, " ERROR: 0:? : 'variable' : is not available in current GLSL version gl_TexCoord."
FYI the gl_TexCoord is the output of the vertex shader which is in build to vizard. Can someone help me to fix it. here is the code:
#version 440
// All uniforms as provided by Vizard
uniform sampler2D vizpp_InputDepthTex; // Depth texture
uniform sampler2D vizpp_InputTex; // Color texture
uniform ivec2 vizpp_InputSize; // Render size of screen in pixels
uniform ivec2 vizpp_InputPixelSize; // Pixel size (1.0/vizpp_InputSize)
uniform mat4 osg_ViewMatrix; // View matrix of camera
uniform mat4 osg_ViewMatrixInverse; // Inverse of view matrix
// Your own uniforms
//uniform sampler2D u_texture;
//uniform sampler2D u_normalTexture;
uniform sampler2D g_FinalSSAO;
const bool onlyAO = false; //Only show AO pass for debugging
const bool externalBlur = false; //Store AO in alpha slot for a later blur
struct ASSAOConstants
{
vec2 ViewportPixelSize; // .zw == 1.0 / ViewportSize.xy
vec2 HalfViewportPixelSize; // .zw == 1.0 / ViewportHalfSize.xy
vec2 DepthUnpackConsts;
vec2 CameraTanHalfFOV;
vec2 NDCToViewMul;
vec2 NDCToViewAdd;
ivec2 PerPassFullResCoordOffset;
vec2 PerPassFullResUVOffset;
vec2 Viewport2xPixelSize;
vec2 Viewport2xPixelSize_x_025; // Viewport2xPixelSize * 0.25 (for fusing add+mul into mad)
float EffectRadius; // world (viewspace) maximum size of the shadow
float EffectShadowStrength; // global strength of the effect (0 - 5)
float EffectShadowPow;
float EffectShadowClamp;
float EffectFadeOutMul; // effect fade out from distance (ex. 25)
float EffectFadeOutAdd; // effect fade out to distance (ex. 100)
float EffectHorizonAngleThreshold; // limit errors on slopes and caused by insufficient geometry tessellation (0.05 to 0.5)
float EffectSamplingRadiusNearLimitRec; // if viewspace pixel closer than this, don't enlarge shadow sampling radius anymore (makes no sense to grow beyond some distance, not enough samples to cover everything, so just limit the shadow growth; could be SSAOSettingsFadeOutFrom * 0.1 or less)
float DepthPrecisionOffsetMod;
float NegRecEffectRadius; // -1.0 / EffectRadius
float LoadCounterAvgDiv; // 1.0 / ( halfDepthMip[SSAO_DEPTH_MIP_LEVELS-1].sizeX * halfDepthMip[SSAO_DEPTH_MIP_LEVELS-1].sizeY )
float AdaptiveSampleCountLimit;
float InvSharpness;
int PassIndex;
vec2 QuarterResPixelSize; // used for importance map only
vec4 PatternRotScaleMatrices[5];
float NormalsUnpackMul;
float NormalsUnpackAdd;
float DetailAOStrength;
float Dummy0;
mat4 NormalsWorldToViewspaceMatrix;
};
uniform ASSAOConstants g_ASSAOConsts;
float PSApply( in vec4 inPos, in vec2 inUV)
{ //inPos = gl_FragCoord;
float ao;
uvec2 pixPos = uvec2(inPos.xy);
uvec2 pixPosHalf = pixPos / uvec2(2, 2);
// calculate index in the four deinterleaved source array texture
int mx = int (pixPos.x % 2);
int my = int (pixPos.y % 2);
int ic = mx + my * 2; // center index
int ih = (1-mx) + my * 2; // neighbouring, horizontal
int iv = mx + (1-my) * 2; // neighbouring, vertical
int id = (1-mx) + (1-my)*2; // diagonal
vec2 centerVal = texelFetchOffset( g_FinalSSAO, ivec2(pixPosHalf), 0, ivec2(ic, 0 ) ).xy;
ao = centerVal.x;
if (true){ // change to 0 if you want to disable last pass high-res blur (for debugging purposes, etc.)
vec4 edgesLRTB = UnpackEdges( centerVal.y );
// convert index shifts to sampling offsets
float fmx = mx;
float fmy = my;
// in case of an edge, push sampling offsets away from the edge (towards pixel center)
float fmxe = (edgesLRTB.y - edgesLRTB.x);
float fmye = (edgesLRTB.w - edgesLRTB.z);
// calculate final sampling offsets and sample using bilinear filter
vec2 uvH = (inPos.xy + vec2( fmx + fmxe - 0.5, 0.5 - fmy ) ) * 0.5 * g_ASSAOConsts.HalfViewportPixelSize;
float aoH = textureLodOffset( g_FinalSSAO, uvH, 0, ivec2(ih , 0) ).x;
vec2 uvV = (inPos.xy + vec2( 0.5 - fmx, fmy - 0.5 + fmye ) ) * 0.5 * g_ASSAOConsts.HalfViewportPixelSize;
float aoV = textureLodOffset( g_FinalSSAO, uvV, 0, ivec2( iv , 0) ).x;
vec2 uvD = (inPos.xy + vec2( fmx - 0.5 + fmxe, fmy - 0.5 + fmye ) ) * 0.5 * g_ASSAOConsts.HalfViewportPixelSize;
float aoD = textureLodOffset( g_FinalSSAO, uvD, 0, ivec2( id , 0) ).x;
// reduce weight for samples near edge - if the edge is on both sides, weight goes to 0
vec4 blendWeights;
blendWeights.x = 1.0;
blendWeights.y = (edgesLRTB.x + edgesLRTB.y) * 0.5;
blendWeights.z = (edgesLRTB.z + edgesLRTB.w) * 0.5;
blendWeights.w = (blendWeights.y + blendWeights.z) * 0.5;
// calculate weighted average
float blendWeightsSum = dot( blendWeights, vec4( 1.0, 1.0, 1.0, 1.0 ) );
ao = dot( vec4( ao, aoH, aoV, aoD ), blendWeights ) / blendWeightsSum;
}
return ao;
}
void main(void)
{
// Get base values
vec2 texCoord = gl_TexCoord[0].st;
vec4 color = texture2D(vizpp_InputTex,texCoord);
float depth = texture2D(vizpp_InputDepthTex,texCoord).x;
// Do not calculate if nothing visible (for VR for instance)
if (depth>=1.0)
{
gl_FragColor = color;
return;
}
float ao = PSApply(gl_FragCoord, texCoord);
// Output the result
if(externalBlur) {
gl_FragColor.rgb = color.rgb;
gl_FragColor.a = ao;
}
else if(onlyAO) {
gl_FragColor.rgb = vec3(ao,ao,ao);
gl_FragColor.a = 1.0;
}
else {
gl_FragColor.rgb = ao*color.rgb;
gl_FragColor.a = 1.0;
}
}
gl_TexCoord is a deprecated Compatibility Profile Built-In Language Variables and is removed after GLSL Version 1.20.
If you want to use gl_TexCoord then you would have to use GLSL version 1.20 (#version 120).
But, you don't need the deprecated compatibility profile built-in language variable at all. Define a Vertex shader output texCoord and use this output rather than gl_TexCoord:
#version 440
out vec2 texCoord;
void main()
{
texCoord = ...;
// [...]
}
Specify a corresponding input in the fragment shader:
#version 440
in vec2 texCoord;
void main()
{
vec4 color = texture2D(vizpp_InputTex, texCoord.st);
// [...]
}

Read and write to a buffer in a fragment shader

I'm trying to use a buffer in a fragment like an append buffer : for each execution of the fragment shader, I get the offset of the buffer, add the data and increment the offset.
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inPosition;
layout(location = 0) out vec4 outColor;
layout(std140, binding = 0) buffer CommandDraw {
uint indexCount;
uint instanceCount;
uint firstIndex;
int vertexOffset;
uint firstInstance;
};
layout(std430, binding = 1) buffer SSBO {
float data[];
};
void addVertex(vec4 pos, vec4 color)
{
uint id = indexCount++;
uint offset = id * 8;
data[offset] = pos.x;
data[offset + 1] = pos.y;
data[offset + 2] = pos.z;
data[offset + 3] = pos.w;
data[offset + 4] = color.x;
data[offset + 5] = color.y;
data[offset + 6] = color.z;
data[offset + 7] = color.w;
}
void main()
{
vec3 color = vec3(0.0);
addVertex(vec4(inPosition, 0.0, 1.0), vec4(1.0, 0.0, 0.0, 1.0));
outColor = vec4(inPosition / 2.0 + vec2(0.5), 0.0, 1.0);
}
I execute the fragment shader with a full screen quad.
The problem is that the final offset (here indexCount) is too low : it should be equal to width * height but the value is lower (around 60 for a extent equal to 1280 * 720).
I think that the problem is due to the parallel execution of the fragment shader : some fragment maybe use the same buffer at the same time.
My question is : is it possible to "lock" the resource like with a std::mutex to avoid fragment using the same resource at the same time ?
There's no need for a mutex; simply use an atomic increment of the index value:
uint id = atomicAdd(indexCount, 1);
All of those atomic functions return the previous value. So your code should be fine.
And there's really no point in making your output SSBO if you're not going to compress the data at all (that is, if you're just writing floats). It's better to make it mirror the types of variables you're writing:
struct Vertex
{
vec4 pos;
vec4 color;
};
layout(std430, binding = 1) writeonly restrict buffer SSBO {
Vertex vertices[];
};

Recolor sprites on the fly

I need to replace colors of the sprite.
Some example founded in google
Here is I've found a looks like working solution for Unity - [How to Use a Shader to Dynamically Swap a Sprite's Colors][2]
How to port it to cocos2d-x? Can someone please help with code examples?
I'm looking for cocos2d-x v3 code snippet. Really looking forward for some help.
The algorithm in the article How to Use a Shader to Dynamically Swap a Sprite's Colors is very simple. It is based on a one dimensional lookup table with 256 entries. This allows the algorithm to map only 256 different colors.
In detail, the new colors (the colors used to replace) are stored in a one dimensional texture with 256 entries. When a color is read from the original texture a key is used to find the new color in the one dimensional swap texture. The key which is used is the red color channel of the original color, this means that all different colors in the original text must also have different red color values. This is another restriction.
The original document (How to Use a Shader to Dynamically Swap a Sprite's Colors) says:
Note that this may not work as expected if two or more colors on the sprite texture share the same red value! When using this method, it's important to keep the red values of the colors in the sprite texture different.
Further the algorithm mix the original color and the swap color by the alpha channel of the swap color. That causes that the swap color is drawn if the swap color is completely opaque and the original color is drawn if the swap color is completely transparent, in between will be linearly interpolated.
A GLSL function with this algorithm is very short and looks somehow like this:
uniform sampler2D u_spriteTexture; // sprite texture
uniform sampler1D u_swapTexture; // lookup texture with swap colors
vec4 SwapColor( vec2 textureCoord )
{
vec4 originalColor = texture( u_spriteTexture, textureCoord.st );
vec4 swapColor = texture( u_swapTexture, originalColor.r );
vec3 finalColor = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
return vec4( finalColor.rgb, originalColor.a );
}
Suggested Algorithm
Reading the suggested shader from the question, I came up to the following solution. The shader is using an algorithm to convert from RGB to hue, saturation, and value and back. I took this idea and introduced my own thoughts.
Performant conversion functions between RGB and HSV can be found at RGB to HSV/HSL/HCY/HCL in HLSL, which can easily translated from HLSL to GLSL:
RGB to HSV
const float Epsilon = 1e-10;
vec3 RGBtoHCV( in vec3 RGB )
{
vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
float C = Q.x - min(Q.w, Q.y);
float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
return vec3(H, C, Q.x);
}
vec3 RGBtoHSV(in vec3 RGB)
{
vec3 HCV = RGBtoHCV(RGB);
float S = HCV.y / (HCV.z + Epsilon);
return vec3(HCV.x, S, HCV.z);
}
HSV to RGB
vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp( vec3(R,G,B), 0.0, 1.0 );
}
vec3 HSVtoRGB(in vec3 HSV)
{
vec3 RGB = HUEtoRGB(HSV.x);
return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}
As in the first algorithm of this answer, again a one dimensional lookup table is of need. But the length of the look up table has not to be exactly 256, it is completely user dependent. The key is not the red channel, it is the hue value which is a clear expression of the color and can easily be calculated as seen in RGBtoHSV and RGBtoHSV. The look-up table however has to, contain a color assignment distributed linearly over the * hue * range from 0 to 1 of the original color.
The algorithm can be defined with the following steps:
Convert the original color to the original hue, saturation, and value
Use the original hue as key to find the swap color in the look up table
Convert the swap color to the swap hue, saturation, and value
Convert the hue of the swap color and the original saturation, and value to a new RGB color
Mix the original color and the new color by the alpha channel of the swap color
With this algorithm any RGB color can be swapped, by keeping the saturation and value of the original color. See the following short and clear GLSL function:
uniform sampler2D u_spriteTexture; // sprite texture
uniform sampler1D u_swapTexture; // lookup texture with swap colors
// the texture coordinate is the hue of the original color
vec4 SwapColor( vec2 textureCoord )
{
vec4 originalColor = texture( u_spriteTexture, textureCoord.st );
vec3 originalHSV = RGBtoHSV( originalColor.rgb );
vec4 lookUpColor = texture( u_swapTexture, originalHSV.x );
vec3 swapHSV = RGBtoHSV( lookUpColor.rgb );
vec3 swapColor = HSVtoRGB( vec3( swapHSV.x, originalHSV.y, originalHSV.z ) );
vec3 finalColor = mix( originalColor.rgb, swapColor.rgb, lookUpColor.a );
return vec4( finalColor.rgb, originalColor.a );
}
Apply to cocos2d-x v3.15
To apply the shader to cocos2d-x v3.15 I adapted the HelloWorldScene.h and HelloWorldScene.cpp in the project cpp-empty-test of the cocos2d-x v3.15 test projects.
The shader can be applied to any sprite a can swap up to 10 color tints, but this can easily be expanded. Note, the shader does not only change a single color, it searches all colors which are similar to a color, even the colors with a completely different saturation or brightness. Each color is swapped with a color, that has a equal saturation and brightness, but a new base color.
The information which swaps the colors, is stored in an array of vec3. The x component contains the hue of the original color, the y component contains the hue of the swap color, and the z component contains an epsilon value, which defines the color range.
The shader source files should be placed in the "resource/shader" subdirectory of the project directory.
Vertex shader shader/colorswap.vert
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
void main()
{
gl_Position = CC_PMatrix * a_position;
cc_FragColor = a_color;
cc_FragTexCoord1 = a_texCoord;
}
Fragment shader shader/colorswap.frag
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
const float Epsilon = 1e-10;
vec3 RGBtoHCV( in vec3 RGB )
{
vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
float C = Q.x - min(Q.w, Q.y);
float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
return vec3(H, C, Q.x);
}
vec3 RGBtoHSV(in vec3 RGB)
{
vec3 HCV = RGBtoHCV(RGB);
float S = HCV.y / (HCV.z + Epsilon);
return vec3(HCV.x, S, HCV.z);
}
vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp( vec3(R,G,B), 0.0, 1.0 );
}
vec3 HSVtoRGB(in vec3 HSV)
{
vec3 RGB = HUEtoRGB(HSV.x);
return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}
#define MAX_SWAP 10
uniform vec3 u_swap[MAX_SWAP];
uniform int u_noSwap;
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
vec3 originalHSV = RGBtoHSV( originalColor.rgb );
vec4 swapColor = vec4( originalColor.rgb, 1.0 );
for ( int i = 0; i < 10 ; ++ i )
{
if ( i >= u_noSwap )
break;
if ( abs( originalHSV.x - u_swap[i].x ) < u_swap[i].z )
{
swapColor.rgb = HSVtoRGB( vec3( u_swap[i].y, originalHSV.y, originalHSV.z ) );
break;
}
}
vec3 finalColor = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
gl_FragColor = vec4( finalColor.rgb, originalColor.a );
}
Header file HelloWorldScene.h:
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#define MAX_COLOR 10
class HelloWorld : public cocos2d::Scene
{
public:
virtual bool init() override;
static cocos2d::Scene* scene();
void menuCloseCallback(Ref* sender);
CREATE_FUNC(HelloWorld);
void InitSwapInfo( int i, const cocos2d::Color3B &sourceCol, const cocos2d::Color3B &swapCol, float deviation );
private:
cocos2d::GLProgram* mProgramExample;
cocos2d::Vec3 mSource[MAX_COLOR];
cocos2d::Vec3 mSwap[MAX_COLOR];
float mDeviation[MAX_COLOR];
cocos2d::Vec3 mSwapInfo[MAX_COLOR];
};
#endif // __HELLOWORLD_SCENE_H__
Source file HelloWorldScene.cpp:
Note, the C++ function RGBtoHue and the GLSL function RGBtoHue, should implement the exactly same algorithm.
The input to the function SwapInfo are RGB colors encoded to cocos2d::Vec3. If the source channels of the RGB colors are bytes (unsigned char), then this can easily converted to cocos2d::Vec3 by cocos2d::Vec3( R / 255.0f, G / 255.0f, B / 255.0f ).
#include "HelloWorldScene.h"
#include "AppMacros.h"
USING_NS_CC;
float RGBtoHue( const cocos2d::Vec3 &RGB )
{
const float Epsilon = 1e-10f;
cocos2d::Vec4 P = (RGB.y < RGB.z) ?
cocos2d::Vec4(RGB.y, RGB.z, -1.0f, 2.0f/3.0f) :
cocos2d::Vec4(RGB.y, RGB.z, 0.0f, -1.0f/3.0f);
cocos2d::Vec4 Q = (RGB.x < P.x) ?
cocos2d::Vec4(P.x, P.y, P.w, RGB.x) :
cocos2d::Vec4(RGB.x, P.y, P.z, P.x);
float C = Q.x - (Q.w < Q.y ? Q.w : Q.y);
float H = fabs((Q.w - Q.y) / (6.0f * C + Epsilon) + Q.z);
return H;
}
cocos2d::Vec3 SwapInfo( const cocos2d::Vec3 &sourceCol, const cocos2d::Vec3 &swapCol, float epsi )
{
return cocos2d::Vec3( RGBtoHue( sourceCol ), RGBtoHue( swapCol ), epsi );
}
void HelloWorld::InitSwapInfo( int i, const cocos2d::Color3B &sourceCol, const cocos2d::Color3B &swapCol, float deviation )
{
mSource[i] = cocos2d::Vec3( sourceCol.r/255.0, sourceCol.g/255.0, sourceCol.b/255.0 );
mSwap[i] = cocos2d::Vec3( swapCol.r/255.0, swapCol.g/255.0, swapCol.b/255.0 );
mDeviation[i] = deviation;
mSwapInfo[i] = SwapInfo( mSource[i], mSwap[i], mDeviation[i] );
}
Scene* HelloWorld::scene()
{
return HelloWorld::create();
}
bool HelloWorld::init()
{
if ( !Scene::init() ) return false;
auto visibleSize = Director::getInstance()->getVisibleSize();
auto origin = Director::getInstance()->getVisibleOrigin();
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback,this));
closeItem->setPosition(origin + Vec2(visibleSize) - Vec2(closeItem->getContentSize() / 2));
auto menu = Menu::create(closeItem, nullptr);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(Vec2(visibleSize / 2) + origin);
mProgramExample = new GLProgram();
mProgramExample->initWithFilenames("shader/colorswap.vert", "shader/colorswap.frag");
mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
mProgramExample->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
mProgramExample->link();
mProgramExample->updateUniforms();
mProgramExample->use();
GLProgramState* state = GLProgramState::getOrCreateWithGLProgram(mProgramExample);
sprite->setGLProgram(mProgramExample);
sprite->setGLProgramState(state);
InitSwapInfo( 0, cocos2d::Color3B( 41, 201, 226 ), cocos2d::Color3B( 255, 0, 0 ), 0.1f );
InitSwapInfo( 1, cocos2d::Color3B( 249, 6, 6 ), cocos2d::Color3B( 255, 255, 0 ), 0.1f );
int noOfColors = 2;
state->setUniformVec3v("u_swap", noOfColors, mSwapInfo);
state->setUniformInt("u_noSwap", noOfColors);
this->addChild(sprite);
return true;
}
void HelloWorld::menuCloseCallback(Ref* sender)
{
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
Compare RGB values instead of Hue
A fragment shader which directly compares RGB colors would look like this:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
const float Epsilon = 1e-10;
vec3 RGBtoHCV( in vec3 RGB )
{
vec4 P = (RGB.g < RGB.b) ? vec4(RGB.bg, -1.0, 2.0/3.0) : vec4(RGB.gb, 0.0, -1.0/3.0);
vec4 Q = (RGB.r < P.x) ? vec4(P.xyw, RGB.r) : vec4(RGB.r, P.yzx);
float C = Q.x - min(Q.w, Q.y);
float H = abs((Q.w - Q.y) / (6.0 * C + Epsilon) + Q.z);
return vec3(H, C, Q.x);
}
vec3 RGBtoHSV(in vec3 RGB)
{
vec3 HCV = RGBtoHCV(RGB);
float S = HCV.y / (HCV.z + Epsilon);
return vec3(HCV.x, S, HCV.z);
}
vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp( vec3(R,G,B), 0.0, 1.0 );
}
vec3 HSVtoRGB(in vec3 HSV)
{
vec3 RGB = HUEtoRGB(HSV.x);
return ((RGB - 1.0) * HSV.y + 1.0) * HSV.z;
}
#define MAX_SWAP 10
uniform vec3 u_orig[MAX_SWAP];
uniform vec3 u_swap[MAX_SWAP];
uniform float u_deviation[MAX_SWAP];
uniform int u_noSwap;
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
vec3 originalHSV = RGBtoHSV( originalColor.rgb );
vec4 swapColor = vec4( originalColor.rgb, 1.0 );
for ( int i = 0; i < 10 ; ++ i )
{
if ( i >= u_noSwap )
break;
if ( all( lessThanEqual( abs(originalColor.rgb - u_orig[i]), vec3(u_deviation[i]) ) ) )
{
vec3 swapHSV = RGBtoHSV( u_swap[i].rgb );
swapColor.rgb = HSVtoRGB( vec3( swapHSV.x, originalHSV.y, originalHSV.z ) );
break;
}
}
vec3 finalColor = mix( originalColor.rgb, swapColor.rgb, swapColor.a );
gl_FragColor = vec4( finalColor.rgb, originalColor.a );
}
Note, the initialization of the uniforms has to be adapt:
int noOfColors = 2;
state->setUniformVec3v("u_orig", noOfColors, mSource);
state->setUniformVec3v("u_swap", noOfColors, mSwap);
state->setUniformFloatv("u_deviation", noOfColors, mDeviation);
state->setUniformInt("u_noSwap", noOfColors);
Extension to the answer
If exactly specified colors should be exchanged, the shader can be much more simplified. For this, the deviations u_deviation have to be restricted (e.g deviation = 0.02;).
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
#define MAX_SWAP 11
uniform vec3 u_orig[MAX_SWAP];
uniform vec3 u_swap[MAX_SWAP];
uniform float u_deviation[MAX_SWAP];
uniform int u_noSwap;
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
vec4 swapColor = vec4( originalColor.rgb, 1.0 );
for ( int i = 0; i < MAX_SWAP ; ++ i )
{
vec3 deltaCol = abs( originalColor.rgb - u_orig[i] );
float hit = step( deltaCol.x + deltaCol.y + deltaCol.z, u_deviation[i] * 3.0 );
swapColor.rgb = mix( swapColor.rgb, u_swap[i].rgb, hit );
}
gl_FragColor = vec4( swapColor.rgb, originalColor.a );
}
If each color in the source texture has an individual color channel (this means the color value is only use for this special color, e.g. red color channel), then the shader code can be further simplified, because only the one channel has to be compared:
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
vec4 swapColor = vec4( originalColor.rgb, 1.0 );
for ( int i = 0; i < MAX_SWAP ; ++ i )
{
float hit = step( abs( originalColor.r - u_orig[i].r ), u_deviation[i] );
swapColor.rgb = mix( swapColor.rgb, u_swap[i].rgb, hit );
}
gl_FragColor = vec4( swapColor.rgb, originalColor.a );
}
A further optimization would bring us back to the first algorithm, which was described in this answer. The big advantage of this algorithm would be, that each color is swapped (except the alpha channel of the swap texture is 0), but no expensive searching in the look up table has to be done in the shader.
Each color will be swapped by the corresponding color according to its red color channel. As mentioned, if a color should not be swapped, the alpha channel of the swap texture has to be set to 0.
A new member mSwapTexture has to be add to the class:
cocos2d::Texture2D* mSwapTexture;
The texture can be easily created, and the uniform texture sampler can be set like this:
#include <array>
.....
std::array< unsigned char, 256 * 4 > swapPlane{ 0 };
for ( int c = 0; c < noOfColors; ++ c )
{
size_t i = (size_t)( mSource[c].x * 255.0 ) * 4;
swapPlane[i+0] = (unsigned char)(mSwap[c].x*255.0);
swapPlane[i+1] = (unsigned char)(mSwap[c].y*255.0);
swapPlane[i+2] = (unsigned char)(mSwap[c].z*255.0);
swapPlane[i+3] = 255;
}
mSwapTexture = new Texture2D();
mSwapTexture->setAliasTexParameters();
cocos2d::Size contentSize;
mSwapTexture->initWithData( swapPlane.data(), swapPlane.size(), Texture2D::PixelFormat::RGBA8888, 256, 1, contentSize );
state->setUniformTexture( "u_swapTexture", mSwapTexture );
The fragment shader would look like this:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
uniform sampler2D u_swapTexture; // lookup texture with 256 swap colors
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
vec4 swapColor = texture2D(u_swapTexture, vec2(originalColor.r, 0.0));
vec3 finalColor = mix(originalColor.rgb, swapColor.rgb, swapColor.a);
gl_FragColor = vec4(finalColor.rgb, originalColor.a);
}
Of course, the lookup key has not always to be the red channel, any other channel is also possible.
Even a combination of 2 color channels would be possible by using a increased two dimensional lookup texture. See the following example which demonstrates the use of look up texture with 1024 entries. The look up table uses the full red channel (256 indices) in the X dimension and the green channel divided by 64 (4 indices) in the Y dimension.
Create a two dimensional look up table:
std::array< unsigned char, 1024 * 4 > swapPlane{ 0 };
for ( int c = 0; c < noOfColors; ++ c )
{
size_t ix = (size_t)( mSource[c].x * 255.0 );
size_t iy = (size_t)( mSource[c].y * 255.0 / 64.0 );
size_t i = ( iy * 256 + ix ) * 4;
swapPlane[i+0] = (unsigned char)(mSwap[c].x*255.0);
swapPlane[i+1] = (unsigned char)(mSwap[c].y*255.0);
swapPlane[i+2] = (unsigned char)(mSwap[c].z*255.0);
swapPlane[i+3] = 255;
}
mSwapTexture = new Texture2D();
mSwapTexture->setAliasTexParameters();
cocos2d::Size contentSize;
mSwapTexture->initWithData( swapPlane.data(), swapPlane.size(), Texture2D::PixelFormat::RGBA8888, 256, 4, contentSize );
And adapt the fragment shader:
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
vec4 swapColor = texture2D(u_swapTexture, originalColor.rg);
vec3 finalColor = mix(originalColor.rgb, swapColor.rgb, swapColor.a);
gl_FragColor = vec4(finalColor.rgb, originalColor.a);
}
Interpolate the texture
Since it is not possible to use GL_LINEAR with the above approach, this has to be emulated, if it would be of need:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
uniform sampler2D u_swapTexture; // lookup texture with 256 swap colors
uniform vec2 u_spriteSize;
void main()
{
vec2 texS = 1.0 / u_spriteSize;
vec2 texF = fract( cc_FragTexCoord1 * u_spriteSize + 0.5 );
vec2 texC = (cc_FragTexCoord1 * u_spriteSize + 0.5 - texF) / u_spriteSize;
vec4 originalColor = texture2D(CC_Texture0, texC);
vec4 swapColor = texture2D(u_swapTexture, originalColor.rg);
vec3 finalColor00 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);
originalColor = texture2D(CC_Texture0, texC+vec2(texS.x, 0.0));
swapColor = texture2D(u_swapTexture, originalColor.rg);
vec3 finalColor10 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);
originalColor = texture2D(CC_Texture0, texC+vec2(0.0,texS.y));
swapColor = texture2D(u_swapTexture, originalColor.rg);
vec3 finalColor01 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);
originalColor = texture2D(CC_Texture0, texC+texS.xy);
swapColor = texture2D(u_swapTexture, originalColor.rg);
vec3 finalColor11 = mix(originalColor.rgb, swapColor.rgb, swapColor.a);
vec3 finalColor0 = mix( finalColor00, finalColor10, texF.x );
vec3 finalColor1 = mix( finalColor01, finalColor11, texF.x );
vec3 finalColor = mix( finalColor0, finalColor1, texF.y );
gl_FragColor = vec4(finalColor.rgb, originalColor.a);
}
The new uniform variable u_spriteSize has to be set like this:
auto size = sprite->getTexture()->getContentSizeInPixels();
state->setUniformVec2( "u_spriteSize", Vec2( (float)size.width, (float)size.height ) );
Modify the texture on the CPU
Of course the texture can also be modified on the CPU, but then for each set of swap colors a separated texture has to be generated. the advantage would be that no more shader is of need.
The following code swaps the colors when the texture is loaded. The shader has to be skipped completely.
Sprite * sprite = nullptr;
std::string imageFile = ....;
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(imageFile);
cocos2d::Image *img = !fullpath.empty() ? new Image() : nullptr;
if (img != nullptr && img->initWithImageFile(fullpath))
{
if ( img->getRenderFormat() == Texture2D::PixelFormat::RGBA8888 )
{
unsigned char *plane = img->getData();
for ( int y = 0; y < img->getHeight(); ++ y )
{
for ( int x = 0; x < img->getWidth(); ++ x )
{
size_t i = ( y * img->getWidth() + x ) * 4;
unsigned char t = plane[i];
for ( int c = 0; c < noOfColors; ++ c )
{
if ( fabs(mSource[c].x - plane[i+0]/255.0f) < mDeviation[c] &&
fabs(mSource[c].y - plane[i+1]/255.0f) < mDeviation[c] &&
fabs(mSource[c].z - plane[i+2]/255.0f) < mDeviation[c] )
{
plane[i+0] = (unsigned char)(mSwap[c].x*255.0);
plane[i+1] = (unsigned char)(mSwap[c].y*255.0);
plane[i+2] = (unsigned char)(mSwap[c].z*255.0);
}
}
}
}
}
std::string key = "my_swap_" + imageFile;
if ( Texture2D *texture = _director->getTextureCache()->addImage( img, key ) )
sprite = Sprite::createWithTexture( texture );
}
Combined approach on the CPU and GPU
This approach can be used if always the same regions (colors) of the texture are swapped. The advantage of this approach is, that the original texture is modified only once, but every application of the texture can hold its own swap table.
For this approach the alpha channel is used to hold the index of the swap color. In the example code below, the value range from 1 to including 11 is used to store the indices of the swap color. 0 is reserved for absolute transparency.
Sprite * sprite = nullptr;
std::string imageFile = ....;
std::string key = "my_swap_" + imageFile;
Texture2D *texture = _director->getTextureCache()->getTextureForKey( key );
if (texture == nullptr)
{
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(imageFile);
cocos2d::Image *img = !fullpath.empty() ? new Image() : nullptr;
if ( img->initWithImageFile(fullpath) &&
img->getRenderFormat() == Texture2D::PixelFormat::RGBA8888 )
{
unsigned char *plane = img->getData();
for ( int y = 0; y < img->getHeight(); ++ y )
{
for ( int x = 0; x < img->getWidth(); ++ x )
{
size_t i = ( y * img->getWidth() + x ) * 4;
unsigned char t = plane[i];
for ( int c = 0; c < noOfColors; ++ c )
{
if ( fabs(mSource[c].x - plane[i+0]/255.0f) < mDeviation[c] &&
fabs(mSource[c].y - plane[i+1]/255.0f) < mDeviation[c] &&
fabs(mSource[c].z - plane[i+2]/255.0f) < mDeviation[c] )
{
plane[i+3] = (unsigned char)(c+1);
}
}
}
}
texture = _director->getTextureCache()->addImage( img, key );
}
}
if ( texture != nullptr )
sprite = Sprite::createWithTexture( texture );
The fragment shader needs only the uniforms u_swap and u_noSwap and does not have to do an expensive searching.
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 cc_FragColor;
varying vec2 cc_FragTexCoord1;
#define MAX_SWAP 11
uniform vec3 u_swap[MAX_SWAP];
uniform int u_noSwap;
void main()
{
vec4 originalColor = texture2D(CC_Texture0, cc_FragTexCoord1);
float fIndex = originalColor.a * 255.0 - 0.5;
float maxIndex = float(u_noSwap) + 0.5;
int iIndex = int( clamp( fIndex, 0.0, maxIndex ) );
float isSwap = step( 0.0, fIndex ) * step( fIndex, maxIndex );
vec3 swapColor = mix( originalColor.rgb, u_swap[iIndex], isSwap );
gl_FragColor = vec4( swapColor.rgb, max(originalColor.a, isSwap) );
}
Change the Hue,Saturation,Value of your sprite using shader.
Shader code example:
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
////uniform sampler2D CC_Texture0;
uniform float u_dH;
uniform float u_dS;
uniform float u_dL;
//algorithm ref to: https://en.wikipedia.org/wiki/HSL_and_HSV
void main() {
vec4 texColor=texture2D(CC_Texture0, v_texCoord);
float r=texColor.r;
float g=texColor.g;
float b=texColor.b;
float a=texColor.a;
//convert rgb to hsl
float h;
float s;
float l;
{
float max=max(max(r,g),b);
float min=min(min(r,g),b);
//----h
if(max==min){
h=0.0;
}else if(max==r&&g>=b){
h=60.0*(g-b)/(max-min)+0.0;
}else if(max==r&&g<b){
h=60.0*(g-b)/(max-min)+360.0;
}else if(max==g){
h=60.0*(b-r)/(max-min)+120.0;
}else if(max==b){
h=60.0*(r-g)/(max-min)+240.0;
}
//----l
l=0.5*(max+min);
//----s
if(l==0.0||max==min){
s=0.0;
}else if(0.0<=l&&l<=0.5){
s=(max-min)/(2.0*l);
}else if(l>0.5){
s=(max-min)/(2.0-2.0*l);
}
}
//(h,s,l)+(dH,dS,dL) -> (h,s,l)
h=h+u_dH;
s=min(1.0,max(0.0,s+u_dS));
l=l;//do not use HSL model to adjust lightness, because the effect is not good
//convert (h,s,l) to rgb and got final color
vec4 finalColor;
{
float q;
if(l<0.5){
q=l*(1.0+s);
}else if(l>=0.5){
q=l+s-l*s;
}
float p=2.0*l-q;
float hk=h/360.0;
float t[3];
t[0]=hk+1.0/3.0;t[1]=hk;t[2]=hk-1.0/3.0;
for(int i=0;i<3;i++){
if(t[i]<0.0)t[i]+=1.0;
if(t[i]>1.0)t[i]-=1.0;
}//got t[i]
float c[3];
for(int i=0;i<3;i++){
if(t[i]<1.0/6.0){
c[i]=p+((q-p)*6.0*t[i]);
}else if(1.0/6.0<=t[i]&&t[i]<0.5){
c[i]=q;
}else if(0.5<=t[i]&&t[i]<2.0/3.0){
c[i]=p+((q-p)*6.0*(2.0/3.0-t[i]));
}else{
c[i]=p;
}
}
finalColor=vec4(c[0],c[1],c[2],a);
}
//actually, it is not final color. the lightness has not been adjusted
//adjust lightness use the simplest method
finalColor+=vec4(u_dL,u_dL,u_dL,0.0);
gl_FragColor=finalColor;
}

OpenGL cannot create uint uniform

I am wondering, why creating uint uniform fails? Is uint type removed in OpenGL 3.x (3.2 core) or just a bug in Nvidia drivers(337.88)?
#version 150
uniform uint picking; // glGetUniformLocation() = -1;
#version 150
uniform int picking; // glGetUniformLocation() = 3;
use in shader:
if(picking == 0) { finalColor = ....

OpenGL: GLSL float has low precision

I have float alpha texture that contains amplitude values. It is converted to decibels and displayed in grayscale.
Here is conversation code (C++) :
const float db_min = -100, db_max = 0;
float image[height][width];
for (int y = 0; y<height; ++y) {
for (int x = 0; x<width; ++x) {
image[y][x]= 20.f * log(a[i])/log(10.f);
image[y][x] = (image[y][x]-db_min)/(db_max-db_min);
}
}
Here is Fragment Shader (GLSL):
#version 120
precision highp float;
varying vec2 texcoord;
uniform sampler2D texture;
void main() {
float value = texture2D(texture, texcoord).a;
gl_FragColor = vec4(value, value, value, 0);
}
Here is a screenshot:
Looks perfect! Now, I want to write conversation in Fragment Shader itself, instead of C++:
#version 120
precision highp float;
varying vec2 texcoord;
uniform sampler2D texture;
const float db_min = -100., db_max = 0.;
void main() {
float value = texture2D(texture, texcoord).a;
value = 20. * log(value)/log(10.);
value = (value-db_min)/(db_max-db_min);
gl_FragColor = vec4(value, value, value, 0);
}
Here is a screenshot:
Why are results different? What am I doing wrong?
Limited texel precision - that could be the problem. I can guess you're keeping your (very-high-range as I understand looking at log) values in a 8-bit per-channel texture(e.g. RGBA8). Then you could use a floating-point format or pack your high-precision/range values to you 4-bytes format(e.g. a fixed point).