Shifting viewport in webGL canvas - webgl

I'm making a game where the user uses WASD keys to move the viewport to see the game world. As of right now, only the initial sprites in viewport are being rendered. I was able to move the viewport via gl.viewport(x, y, width, height) by updating its x and y, but when I move to the viewport to different world space, the screen is black and there should be sprites. No errors in the log and updates are being computed. I cleared the screen each drawing and
private drawSprite( webGL : WebGLRenderingContext,
viewport : Viewport,
sprite : AnimatedSprite) : void {
let canvasWidth : number = webGL.canvas.width;
let canvasHeight : number = webGL.canvas.height;
let spriteType : AnimatedSpriteType = sprite.getSpriteType();
let texture : WebGLGameTexture = spriteType.getSpriteSheetTexture();
let spriteWidth : number = spriteType.getSpriteWidth();
let spriteHeight : number = spriteType.getSpriteHeight();
let spriteXInPixels : number = sprite.getPosition().getX() + (spriteWidth/2);
let spriteYInPixels : number = sprite.getPosition().getY() + (spriteHeight/2);
let spriteXTranslate : number = (spriteXInPixels - (canvasWidth/2))/(canvasWidth/2);
let spriteYTranslate : number = (spriteYInPixels - (canvasHeight/2))/(canvasHeight/2);
this.meshTranslate.setX(spriteXTranslate);
this.meshTranslate.setY(-spriteYTranslate);
let defaultWidth : number = canvasWidth;
let defaultHeight : number = canvasHeight;
let scaleX : number = 2*spriteWidth/defaultWidth;
let scaleY : number = 2*spriteHeight/defaultHeight;
this.meshScale.set(scaleX, scaleY, 0.0, 0.0);//1.0, 1.0);
let rotation: Vector3 = sprite.getRotation()
this.meshRotate.setThetaZ(rotation.getThetaZ())
MathUtilities.identity(this.meshTransform);
MathUtilities.model(this.meshTransform, this.meshTranslate, this.meshRotate, this.meshScale);
// FIGURE OUT THE TEXTURE COORDINATE FACTOR AND SHIFT
let texCoordFactorX : number = spriteWidth/texture.width;
let texCoordFactorY : number = spriteHeight/texture.height;
let spriteLeft : number = sprite.getLeft();
let spriteTop : number = sprite.getTop();
let texCoordShiftX : number = spriteLeft/texture.width;
let texCoordShiftY : number = spriteTop/texture.height;
webGL.bindBuffer(webGL.ARRAY_BUFFER, this.vertexDataBuffer);
webGL.bindTexture(webGL.TEXTURE_2D, texture.webGLTexture);
let a_PositionLocation : GLuint = this.webGLAttributeLocations.get(this.A_POSITION);
webGL.vertexAttribPointer(a_PositionLocation, this.FLOATS_PER_TEXTURE_COORDINATE, webGL.FLOAT, false, this.TOTAL_BYTES, this.VERTEX_POSITION_OFFSET);
webGL.enableVertexAttribArray(a_PositionLocation);
let a_TexCoordLocation : GLuint = this.webGLAttributeLocations.get(this.A_TEX_COORD);
webGL.vertexAttribPointer(a_TexCoordLocation, this.FLOATS_PER_TEXTURE_COORDINATE, webGL.FLOAT, false, this.TOTAL_BYTES, this.TEXTURE_COORDINATE_OFFSET);
webGL.enableVertexAttribArray(a_TexCoordLocation);
let u_MeshTransformLocation : WebGLUniformLocation = this.webGLUniformLocations.get(this.U_MESH_TRANSFORM);
webGL.uniformMatrix4fv(u_MeshTransformLocation, false, this.meshTransform.getData());
let u_SamplerLocation : WebGLUniformLocation = this.webGLUniformLocations.get(this.U_SAMPLER);
webGL.uniform1i(u_SamplerLocation, texture.webGLTextureId);
let u_TexCoordFactorLocation : WebGLUniformLocation = this.webGLUniformLocations.get(this.U_TEX_COORD_FACTOR);
webGL.uniform2f(u_TexCoordFactorLocation, texCoordFactorX, texCoordFactorY);
let u_TexCoordShiftLocation : WebGLUniformLocation = this.webGLUniformLocations.get(this.U_TEX_COORD_SHIFT);
webGL.uniform2f(u_TexCoordShiftLocation, texCoordShiftX, texCoordShiftY);
webGL.drawArrays(webGL.TRIANGLE_STRIP, this.INDEX_OF_FIRST_VERTEX, this.NUM_VERTICES);
}
The vertex shader
uniform mat4 u_MeshTransform;
uniform vec2 u_TexCoordFactor;
uniform vec2 u_TexCoordShift;
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = u_MeshTransform * a_Position;
vec2 tempTexCoord = a_TexCoord * u_TexCoordFactor;
v_TexCoord = tempTexCoord + u_TexCoordShift;
}
FragShader
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
Any idea why the moving viewport wouldn't render the sprites in that space. I'm positive all the values are updated. TIA.

gl.viewport only sets the conversion from normalized device coordinates to pixel space. It won't help you move the view around.
If you want to move the view around you usually add in a view matrix either in your math in Javascript where you compute meshTransform or else by adding another matrix to your shader
gl_Position = u_viewProjectionTransform * u_MeshTransform * a_Position;
or
gl_Position = u_projectionTransform * u_viewTransform * u_MeshTranform * a_Position;
You then set u_projectionTransform for your projection (perspective or orthographic) and you set u_viewTransform for your camera

Related

OpenGL-ES: selective blending

I'm using GPUImage's corner detector for extracting corners from camera captured frames. I'd like to render sparkles at the corners coordinates. When I've got the corners coordinates I pass it to my sparkles generator (pretty similar to GPUImage's crosshair generator):
public func renderSparkles(_ positions:[Position]) {
imageFramebuffer.activateFramebufferForRendering()
imageFramebuffer.timingStyle = .stillImage
glEnable(GLenum(GL_POINT_SPRITE_OES))
sparklesShader.use()
uniformSettings.restoreShaderSettings(sparklesShader)
clearFramebufferWithColor(Color.transparent)
guard let positionAttribute = sparklesShader.attributeIndex("position") else {
fatalError("A position attribute was missing from the shader program during rendering.")
}
let convertedPositions = positions.flatMap{$0.toGLArray()}
glVertexAttribPointer(positionAttribute, 2, GLenum(GL_FLOAT), 0, 0, convertedPositions)
glDrawArrays(GLenum(GL_POINTS), 0, GLsizei(positions.count))
notifyTargets()
}
In the fragment shader of generator I draw sparkle at the corner coordinate:
uniform lowp vec3 crosshairColor;
varying highp vec2 centerLocation;
void main()
{
lowp vec2 distanceFromCenter = abs(centerLocation - gl_PointCoord.xy);
lowp float r = length(distanceFromCenter)*0.042;
lowp float a = atan(distanceFromCenter.y,distanceFromCenter.x);
lowp float f = abs(cos(a*2.0))*0.100*-0.322/0.224;
lowp float b = abs(cos(a*2.0))*0.078*-0.882/17.088;
lowp float c = abs(cos(a*2.0))*0.030*-0.178*0.688;
lowp vec4 color = vec4(1.0-smoothstep(f,b+0.04,r)) + vec4(1.0-smoothstep(b,c+0.02,r/0.644)) + vec4(1.0-smoothstep(b,c+0.011,r/0.764));
gl_FragColor = color;
}
Here is an example of output I've got
How could I blend the sparkles?
In case if someone else will find it helpful, here is the code of rendering sparkles method based on Rabbid76 suggestion:
public func renderSparkles(_ positions:[Position]) {
imageFramebuffer.activateFramebufferForRendering()
imageFramebuffer.timingStyle = .stillImage
glEnable(GLenum(GL_POINT_SPRITE_OES))
glEnable(GLenum(GL_BLEND))
glBlendFunc(GLenum(GL_SRC_ALPHA),GLenum(GL_ONE_MINUS_CONSTANT_ALPHA))
sparklesShader.use()
uniformSettings.restoreShaderSettings(sparklesShader)
clearFramebufferWithColor(Color.transparent)
guard let positionAttribute = sparklesShader.attributeIndex("position") else {
fatalError("A position attribute was missing from the shader program during rendering.")
}
let convertedPositions = positions.flatMap{$0.toGLArray()}
glVertexAttribPointer(positionAttribute, 2, GLenum(GL_FLOAT), 0, 0, convertedPositions)
glDrawArrays(GLenum(GL_POINTS), 0, GLsizei(positions.count))
notifyTargets()
}

WEBGL Fluid simulation

I am trying to get a fluid simulation to work using WebGL using http://meatfighter.com/fluiddynamics/GPU_Gems_Chapter_38.pdf as a resource. I have implemented everything but I feel like there are multiple things that aren't working correctly. I added boundaries but it seems like they are having no effect, which makes me suspicious about how much pressure and advection are working. I displayed the divergence and I get very little around where I am moving the object around as well as when the velocity hits the edge (boundary), but the pressure that I get is completely empty. I calculate pressure using the diffusion shader as described in the linked resource.
I know the code I am posting is al little confusing due to the nature of what it is about. I can supply any pictures/links to the simulation if that would help.
--EDIT--
After some more investigation I believe the problem is related to my advection function. or at least a problem. I am unsure how to fix it though.
Instead of posting all of my code, the general process I follow is:
advect velocity
diffuse velocity
add velocity
calculate divergence
compute pressure
subtract gradient
for diffusing velocity and computing pressure I am only do 10 iterations because thats all my computer can handle with my implementation (I will optimize once I get it working), but I feel like the computing pressure and subtracting gradient are not having any effect.
here are the shaders I am using:
//advection
uniform vec2 res;//The width and height of our screen
uniform sampler2D velocity;//input velocity
uniform sampler2D quantity;//quantity to advect
void main() {
vec2 pixel = gl_FragCoord.xy / res.xy;
float i0, j0, i1, j1;
float x, y, s0, s1, t0, t1, dxt0, dyt0;
float dt = 1.0/60.0;
float Nx = res.x -1.0;
float Ny = res.y -1.0;
float i = pixel.x;
float j = pixel.y;
dxt0 = dt ;
dyt0 = dt ;
x = gl_FragCoord.x - dxt0 * (texture2D(velocity, pixel).x );
y = gl_FragCoord.y - dyt0 * (texture2D(velocity, pixel).y );
i0=x-0.5;
i1=x+0.5;
j0=y-0.5;
j1=y+0.5;
s1 = x-i0;
s0 = 1.0-s1;
t1 = y-j0;
t0 = 1.0-t1;
float p1 = (t0 * texture2D(quantity, vec2(i0,j0)/res.xy).r);
float p2 = (t1 * texture2D(quantity, vec2(i0,j1)/res.xy).r);
float p3 = (t0 * texture2D(quantity, vec2(i1,j0)/res.xy).r);
float p4 = (t1 * texture2D(quantity, vec2(i1,j1)/res.xy).r);
float total1 = s0 * (p1 + p2);
float total2 = s1 * (p3 + p4);
gl_FragColor.r = total1 + total2;
p1 = (t0 * texture2D(quantity, vec2(i0,j0)/res.xy).g);
p2 = (t1 * texture2D(quantity, vec2(i0,j1)/res.xy).g);
p3 = (t0 * texture2D(quantity, vec2(i1,j0)/res.xy).g);
p4 = (t1 * texture2D(quantity, vec2(i1,j1)/res.xy).g);
total1 = s0 * (p1 + p2);
total2 = s1 * (p3 + p4);
gl_FragColor.g = total1 + total2;
}
//diffusion shader starts here
uniform vec2 res;//The width and height of our screen
uniform sampler2D x;//Our input texture
uniform sampler2D b;
uniform float alpha;
uniform float rBeta;
void main() {
float xPixel = 1.0/res.x;
float yPixel = 1.0/res.y;
vec2 pixel = gl_FragCoord.xy / res.xy;
gl_FragColor = texture2D( b, pixel );
vec4 leftColor = texture2D(x,vec2(pixel.x-xPixel,pixel.y));
vec4 rightColor = texture2D(x,vec2(pixel.x+xPixel,pixel.y));
vec4 upColor = texture2D(x,vec2(pixel.x,pixel.y-yPixel));
vec4 downColor = texture2D(x,vec2(pixel.x,pixel.y+yPixel));
gl_FragColor.r = (gl_FragColor.r * alpha +leftColor.r + rightColor.r + upColor.r + downColor.r) * rBeta;
gl_FragColor.g = (gl_FragColor.g * alpha +leftColor.g + rightColor.g + upColor.g + downColor.g)* rBeta;
gl_FragColor.b = (gl_FragColor.b * alpha +leftColor.b + rightColor.b + upColor.b + downColor.b)* rBeta;
}
//gradient
uniform vec2 res;//The width and height of our screen
uniform sampler2D velocity;//Our input velocity
uniform sampler2D pressure;//Our input pressure
void main() {
float xPixel = 1.0/res.x;
float yPixel = 1.0/res.y;
vec2 pixel = gl_FragCoord.xy / res.xy;
vec4 leftColor = texture2D(pressure, vec2(pixel.x-xPixel,pixel.y));
vec4 rightColor = texture2D(pressure, vec2(pixel.x+xPixel,pixel.y));
vec4 upColor = texture2D(pressure, vec2(pixel.x,pixel.y-yPixel));
vec4 downColor = texture2D(pressure, vec2(pixel.x,pixel.y+yPixel));
vec2 gradient = xPixel/2.0 * vec2((rightColor.x - leftColor.x), (upColor.y - downColor.y));
//Diffuse equation
gl_FragColor = texture2D(velocity, pixel) ;
gl_FragColor.xy -= gradient;
}
uniform vec2 res;//The width and height of our screen
uniform sampler2D velocity;//Our input texture
void main() {
float xPixel = 1.0/res.x;
float yPixel = 1.0/res.y;
vec2 pixel = gl_FragCoord.xy / res.xy;
vec4 leftColor = texture2D(velocity, vec2(pixel.x-xPixel,pixel.y));
vec4 rightColor = texture2D(velocity, vec2(pixel.x+xPixel,pixel.y));
vec4 upColor = texture2D(velocity, vec2(pixel.x,pixel.y-yPixel));
vec4 downColor = texture2D(velocity, vec2(pixel.x,pixel.y+yPixel));
float div = xPixel/2.0 * ((rightColor.x - leftColor.x) + (upColor.y - downColor.y));
//Diffuse equation
gl_FragColor = vec4(div);
}

GPUImage add hue/color adjustments per-RGB channel (adjust reds to be more pink or orange)

Stumped trying to adjust the hue of a specific channel (or perhaps, more specifically, a specific range of colors - in this case, reds). Looking at the hue filter, I thought maybe I might get somewhere by commenting out the green and blue modifiers, impacting the changes on only the red channel:
precision highp float;
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform mediump float hueAdjust;
const highp vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
const highp vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);
const highp vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);
const highp vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);
const highp vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);
const highp vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);
void main ()
{
// Sample the input pixel
highp vec4 color = texture2D(inputImageTexture, textureCoordinate);
// Convert to YIQ
highp float YPrime = dot (color, kRGBToYPrime);
highp float I = dot (color, kRGBToI);
highp float Q = dot (color, kRGBToQ);
// Calculate the hue and chroma
highp float hue = atan (Q, I);
highp float chroma = sqrt (I * I + Q * Q);
// Make the user's adjustments
hue += (-hueAdjust); //why negative rotation?
// Convert back to YIQ
Q = chroma * sin (hue);
I = chroma * cos (hue);
// Convert back to RGB
highp vec4 yIQ = vec4 (YPrime, I, Q, 0.0);
color.r = dot (yIQ, kYIQToR);
// --> color.g = dot (yIQ, kYIQToG);
// --> color.b = dot (yIQ, kYIQToB);
// Save the result
gl_FragColor = color;
}
);
But that just leaves the photo either grey/blue and washed-out or purplish green. Am I on the right track? If not, how can I modify this filter to affect individual channels while leaving the others intact?
Some examples:
Original, and the effect I'm trying to achieve:
(The second image is almost unnoticeably different, however the red channel's hue has been made slightly more pinker. I need to be able to adjust it between pink<->orange).
But here's what I get with B and G commented out:
(Left side: <0º, right side: >0º)
It looks to me like it's not affecting the hue of the reds in the way I'd like it to; possibly I'm approaching this incorrectly, or if I'm on the right track, this code isn't correctly adjusting the red channel hue?
(I also tried to achieve this effect using the GPUImageColorMatrixFilter, but I didn't get very far with it).
Edit: here's my current iteration of the shader using #VB_overflow's code + GPUImage wrapper, which is functionally affecting the input image in a way similar to what I'm aiming for:
#import "GPUImageSkinToneFilter.h"
#implementation GPUImageSkinToneFilter
NSString *const kGPUImageSkinToneFragmentShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
// [-1;1] <=> [pink;orange]
uniform highp float skinToneAdjust; // will make reds more pink
// Other parameters
uniform mediump float skinHue;
uniform mediump float skinHueThreshold;
uniform mediump float maxHueShift;
uniform mediump float maxSaturationShift;
// RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
highp vec3 rgb2hsv(highp vec3 c)
{
highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
highp float d = q.x - min(q.w, q.y);
highp float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
// HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
highp vec3 hsv2rgb(highp vec3 c)
{
highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
// Main
void main ()
{
// Sample the input pixel
highp vec4 colorRGB = texture2D(inputImageTexture, textureCoordinate);
// Convert color to HSV, extract hue
highp vec3 colorHSV = rgb2hsv(colorRGB.rgb);
highp float hue = colorHSV.x;
// check how far from skin hue
highp float dist = hue - skinHue;
if (dist > 0.5)
dist -= 1.0;
if (dist < -0.5)
dist += 1.0;
dist = abs(dist)/0.5; // normalized to [0,1]
// Apply Gaussian like filter
highp float weight = exp(-dist*dist*skinHueThreshold);
weight = clamp(weight, 0.0, 1.0);
// We want more orange, so increase saturation
if (skinToneAdjust > 0.0)
colorHSV.y += skinToneAdjust * weight * maxSaturationShift;
// we want more pinks, so decrease hue
else
colorHSV.x += skinToneAdjust * weight * maxHueShift;
// final color
highp vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);
// display
gl_FragColor = vec4(finalColorRGB, 1.0);
}
);
#pragma mark -
#pragma mark Initialization and teardown
#synthesize skinToneAdjust;
#synthesize skinHue;
#synthesize skinHueThreshold;
#synthesize maxHueShift;
#synthesize maxSaturationShift;
- (id)init
{
if(! (self = [super initWithFragmentShaderFromString:kGPUImageSkinToneFragmentShaderString]) )
{
return nil;
}
skinToneAdjustUniform = [filterProgram uniformIndex:#"skinToneAdjust"];
skinHueUniform = [filterProgram uniformIndex:#"skinHue"];
skinHueThresholdUniform = [filterProgram uniformIndex:#"skinHueThreshold"];
maxHueShiftUniform = [filterProgram uniformIndex:#"maxHueShift"];
maxSaturationShiftUniform = [filterProgram uniformIndex:#"maxSaturationShift"];
self.skinHue = 0.05;
self.skinHueThreshold = 50.0;
self.maxHueShift = 0.14;
self.maxSaturationShift = 0.25;
return self;
}
#pragma mark -
#pragma mark Accessors
- (void)setSkinToneAdjust:(CGFloat)newValue
{
skinToneAdjust = newValue;
[self setFloat:newValue forUniform:skinToneAdjustUniform program:filterProgram];
}
- (void)setSkinHue:(CGFloat)newValue
{
skinHue = newValue;
[self setFloat:newValue forUniform:skinHueUniform program:filterProgram];
}
- (void)setSkinHueThreshold:(CGFloat)newValue
{
skinHueThreshold = newValue;
[self setFloat:newValue forUniform:skinHueThresholdUniform program:filterProgram];
}
- (void)setMaxHueShift:(CGFloat)newValue
{
maxHueShift = newValue;
[self setFloat:newValue forUniform:maxHueShiftUniform program:filterProgram];
}
- (void)setMaxSaturationShift:(CGFloat)newValue
{
maxSaturationShift = newValue;
[self setFloat:newValue forUniform:maxSaturationShiftUniform program:filterProgram];
}
#end
I made an example on ShaderToy. Use latest Chrome to see it, on my side it does not work on Firefox or IE because it uses a video as input.
After some experiments it seems to me that for red hues to be more "pink" you need to decrease the hue, but to get more "orange" you need to increase saturation.
In the code I convert to HSV instead of YIQ because this is faster, makes tweaking saturation possible and still allow to tweak hue. Also HSV components are in a [0-1] interval, so no need to handle radians.
So here is how this is done :
You choose a reference hue or color (in your case a red hue)
Shader compute the "distance" from current pixel hue to ref hue
Based on this distance, decrease hue if you want pink, increase saturation if you want orange
It is important to note that hue behaves differently than saturation and value: it should be treated as an angle (more info here).
The reference hue should be hardcoded, chosen by user (by color picking image), or found by analysing image content.
There are many different possible ways the compute the distance, in the example I chose to use the angular distance between hues.
You also need to apply some kind of filtering after computing the distance to "select" only closest colors, like this gaussian like function.
Here is the code, without the ShaderToy stuff:
precision highp float;
// [-1;1] <=> [pink;orange]
const float EFFECT_AMOUNT = -0.25; // will make reds more pink
// Other parameters
const float SKIN_HUE = 0.05;
const float SKIN_HUE_TOLERANCE = 50.0;
const float MAX_HUE_SHIFT = 0.04;
const float MAX_SATURATION_SHIFT = 0.25;
// RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
// HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
// Main
void main ()
{
// Sample the input pixel
vec4 colorRGB = texture2D(inputImageTexture, textureCoordinate);
// get effect amount to apply
float skin_tone_shift = EFFECT_AMOUNT;
// Convert color to HSV, extract hue
vec3 colorHSV = rgb2hsv(colorRGB.rgb);
float hue = colorHSV.x;
// check how far from skin hue
float dist = hue - SKIN_HUE;
if (dist > 0.5)
dist -= 1.0;
if (dist < -0.5)
dist += 1.0;
dist = abs(dist)/0.5; // normalized to [0,1]
// Apply Gaussian like filter
float weight = exp(-dist*dist*SKIN_HUE_TOLERANCE);
weight = clamp(weight, 0.0, 1.0);
// We want more orange, so increase saturation
if (skin_tone_shift > 0.0)
colorHSV.y += skin_tone_shift * weight * MAX_SATURATION_SHIFT;
// we want more pinks, so decrease hue
else
colorHSV.x += skin_tone_shift * weight * MAX_HUE_SHIFT;
// final color
vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);
// display
gl_FragColor = vec4(finalColorRGB, 1.0);
}
More Orange:
More Pink:
--EDIT--
It seems to me that you are not setting the uniform values in your ObjectiveC code. If you forget this shader will get zero for all those.
Code should look like this :
- (id)init
{
if(! (self = [super initWithFragmentShaderFromString:kGPUImageSkinToneFragmentShaderString]) )
{
return nil;
}
skinToneAdjustUniform = [filterProgram uniformIndex:#"skinToneAdjust"];
[self setFloat:0.5 forUniform:skinToneAdjustUniform program:filterProgram]; // here 0.5 so should increase saturation
skinHueUniform = [filterProgram uniformIndex:#"skinHue"];
self.skinHue = 0.05;
[self setFloat:self.skinHue forUniform:skinHueUniform program:filterProgram];
skinHueToleranceUniform = [filterProgram uniformIndex:#"skinHueTolerance"];
self.skinHueTolerance = 50.0;
[self setFloat:self.skinHueTolerance forUniform:skinHueToleranceUniform program:filterProgram];
maxHueShiftUniform = [filterProgram uniformIndex:#"maxHueShift"];
self.maxHueShift = 0.04;
[self setFloat:self.maxHueShift forUniform:maxHueShiftUniform program:filterProgram];
maxSaturationShiftUniform = [filterProgram uniformIndex:#"maxSaturationShift"];
self.maxSaturationShift = 0.25;
[self setFloat:self.maxSaturationShift forUniform:maxSaturationShiftUniform program:filterProgram];
return self;
}
#end

WebGL: Zooming to and stopping at object in a scene in WebGL

We've created a WebGl application which displays a scene containing multiple objects. The entire scene can be rotated in multiple directions. The application requires the user to be able to zoom up to but NOT thru the object. I know this functionality can be implemented using webgl frameworks such as Three.js and SceneJs. Unfortunately, our application is not leveraging a framework. Is there a way to implement the zoom functionality described here using webgl only? Note: I don't believe object picking will work for us since the user is not required to select any object in the scene. Thanks for your help.
Off the top of my head.
First off you need to know the size of each object in world space. For example if one object is 10 units big and another is 100 units big you probably want to be a different distance from the 100 unit object as the 10 unit object. By world space I also mean if you're scaling the 10 unit object by 9 then in world space it would be 90 units big and again you'd want to get a different distance away then if it was 10 units
You generally compute the size of an object in local space by computing the extents of its vertices. Just go through all the vertices and keep track of the min and max values in x, y, and z. Whether you want to take the biggest value from the object's origin or compute an actual center point is up to you.
So, given the size we can compute how far away you need to be to see the entire object. For the standard perspective matrix you can just work backward. If you know your object is 10 units big then you need to fit 10 units in your frustum. You'd probably actually pick something like 14 units (say size * 1.4) so there's some space around the object.
We know halfFovy, halfSizeToFitOnScreen, we need to compute distance
sohcahtoa
tangent = opposite / adjacent
opposite = halfsizeToFitOnScreen
adjacent = distance
tangent = Math.tan(halfFovY)
Therefore
tangent = sizeToFitOnScreen / distance
tangent * distance = sizeToFitOnScreen
distance = sizeToFitOnScreen / tangent
distance = sizeToFitOnScreen / Math.tan(halfFovY)
So now we know the camera needs to be distance away from the object. There's an entire sphere that's distance away from the object. Where you pick on that sphere is up to you. Assuming you go from where the camera currently is you can compute the direction from the object to the camera
direction = normalize(cameraPos - objectPos)
Now you can compute a point distance away in that direction.
desiredCameraPosition = direction * distance
Now either put the camera there using some lookAt function
matrix = lookAt(desiredCameraPosition, objectPosition, up)
Or lerp between where the camera currently is to it's new desired position
var m4 = twgl.m4;
var v3 = twgl.v3;
twgl.setAttributePrefix("a_");
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var shapes = [
twgl.primitives.createCubeBufferInfo(gl, 2),
twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12),
twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1),
];
function rand(min, max) {
return min + Math.random() * (max - min);
}
function easeInOut(t, start, end) {
var c = end - start;
if ((t /= 0.5) < 1) {
return c / 2 * t * t + start;
} else {
return -c / 2 * ((--t) * (t - 2) - 1) + start;
}
}
// Shared values
var lightWorldPosition = [1, 8, -10];
var lightColor = [1, 1, 1, 1];
var camera = m4.identity();
var view = m4.identity();
var viewProjection = m4.identity();
var targetNdx = 0;
var targetTimer = 0;
var zoomTimer = 0;
var eye = v3.copy([1, 4, -60]);
var target = v3.copy([0, 0, 0]);
var up = [0, 1, 0];
var zoomScale = 1.4;
var zoomDuration = 2;
var targetChangeInterval = 3;
var oldEye;
var oldTarget;
var newEye;
var newTarget;
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
255,255,255,255,
192,192,192,255,
192,192,192,255,
255,255,255,255]));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
var objects = [];
var drawObjects = [];
var numObjects = 100;
var baseHue = rand(0, 360);
for (var ii = 0; ii < numObjects; ++ii) {
var uniforms = {
u_lightWorldPos: lightWorldPosition,
u_lightColor: lightColor,
u_diffuseMult: chroma.hsv((baseHue + rand(0, 60)) % 360, 0.4, 0.8).gl(),
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
u_viewInverse: camera,
u_world: m4.identity(),
u_worldInverseTranspose: m4.identity(),
u_worldViewProjection: m4.identity(),
};
drawObjects.push({
programInfo: programInfo,
bufferInfo: shapes[ii % shapes.length],
uniforms: uniforms,
});
objects.push({
translation: [rand(-50, 50), rand(-50, 50), rand(-50, 50)],
scale: rand(1, 5),
size: 2,
xSpeed: rand(0.2, 0.7),
zSpeed: rand(0.2, 0.7),
uniforms: uniforms,
});
}
var then = Date.now() * 0.001;
function render() {
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var time = Date.now() * 0.001;
var elapsed = time - then;
then = time;
var radius = 6;
var fovy = 30 * Math.PI / 180;
var projection = m4.perspective(fovy, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 200);
targetTimer -= elapsed;
if (targetTimer <= 0) {
targetTimer = targetChangeInterval;
zoomTimer = 0;
targetNdx = (targetNdx + 1) % objects.length;
oldEye = v3.copy(eye);
oldTarget = v3.copy(target);
var targetObj = objects[targetNdx];
newTarget = targetObj.translation;
var halfSize = targetObj.size * targetObj.scale * zoomScale * 0.5;
var distance = halfSize / Math.tan(fovy * 0.5);
var direction = v3.normalize(v3.subtract(eye, newTarget));
newEye = v3.add(newTarget, v3.mulScalar(direction, distance));
}
zoomTimer += elapsed;
var lerp = easeInOut(Math.min(1, zoomTimer / zoomDuration), 0, 1);
eye = v3.lerp(oldEye, newEye, lerp);
target = v3.lerp(oldTarget, newTarget, lerp);
m4.lookAt(eye, target, up, camera);
m4.inverse(camera, view);
m4.multiply(projection, view, viewProjection);
objects.forEach(function(obj, ndx) {
var uni = obj.uniforms;
var world = uni.u_world;
m4.identity(world);
m4.translate(world, obj.translation, world);
m4.rotateX(world, time * obj.xSpeed, world);
m4.rotateZ(world, time * obj.zSpeed, world);
m4.scale(world, [obj.scale, obj.scale, obj.scale], world);
m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose);
m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection);
});
twgl.drawObjectList(gl, drawObjects);
requestAnimationFrame(render);
}
render();
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<script src="//twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chroma-js/0.6.3/chroma.min.js"></script>
<canvas id="c"></canvas>
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (u_worldViewProjection * a_position);
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_diffuseMult;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
abs(l),//max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord) * u_diffuseMult;
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>

Drawing a grid in a WebGL fragment shader

I'm working on porting a ZUI from SVG over to WebGL for a few reasons, and I'd like to render a grid using a fragment shader.
Here's the basic effect I'm going for https://dl.dropboxusercontent.com/u/412963/steel/restel_2.mp4
I'd like to have a triangle that has thin, 1px lines every 10 units, and a thicker 2px line every 100 units (the units here being arbitrary but consistent with world-space, not screen-space).
Here's what I have so far, without the secondary thicker lines like in the video (note that this is literally a copy from my open buffer, and obviously isn't right):
Vertex Shader:
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying float vX;
varying float vY;
void main(void) {
vX = aVertexPosition.x;
vY = aVertexPosition.y;
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
Fragment Shader:
precision mediump float;
uniform vec2 resolution;
uniform float uZoomFactor;
varying float vX;
varying float vY;
void main(void) {
float distance = gl_FragCoord.z / gl_FragCoord.w;
float fuzz = 1.0 / distance;
float minorLineFreq;
if (distance > 10.0) {
minorLineFreq = 1.0;
} else if (distance > 5.0) {
minorLineFreq = 1.0;
} else {
minorLineFreq = 0.10;
}
float xd = mod(vX, minorLineFreq) * 88.1;
float yd = mod(vY, minorLineFreq) * 88.1;
if (xd < fuzz) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
} else if (yd < fuzz) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
} else {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
It produces approximately the right image at a certain distance (but notice the banding effect where there's 2px lines instead of 1px):
Grid with banding
Zoomed in grid with unwanted thicker lines
So, how can I get a consistent grid, with 1px thick lines at every distance, all inside of a WebGL fragment shader?
I believe I've found an acceptable solution.
Using the following vertices (drawn in a triangle strip):
[ 1.0 1.0 0.0
-1.0 1.0 0.0
1.0 -1.0 0.0
-1.0 -1.0 0.0]
Vertex shader:
attribute vec4 aVertexPosition;
void main(void) {
gl_Position = aVertexPosition;
}
Fragment Shader:
precision mediump float;
uniform float vpw; // Width, in pixels
uniform float vph; // Height, in pixels
uniform vec2 offset; // e.g. [-0.023500000000000434 0.9794000000000017], currently the same as the x/y offset in the mvMatrix
uniform vec2 pitch; // e.g. [50 50]
void main() {
float lX = gl_FragCoord.x / vpw;
float lY = gl_FragCoord.y / vph;
float scaleFactor = 10000.0;
float offX = (scaleFactor * offset[0]) + gl_FragCoord.x;
float offY = (scaleFactor * offset[1]) + (1.0 - gl_FragCoord.y);
if (int(mod(offX, pitch[0])) == 0 ||
int(mod(offY, pitch[1])) == 0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
} else {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
Gives results (depending on the pitch and offset) like:
gl_FragCoord is already scaled to the render target resolution. So you can simply:
precision mediump float;
vec4 color = vec4(1.);
vec2 pitch = vec2(50., 50.);
void main() {
if (mod(gl_FragCoord.x, pitch[0]) < 1. ||
mod(gl_FragCoord.y, pitch[1]) < 1.) {
gl_FragColor = color;
} else {
gl_FragColor = vec4(0.);
}
}
https://glslsandbox.com/e#74754.0

Resources