How to convert world coordinates to screen coordinates in OpenGL ES 2.0 - opengl-es

I am using following OpenGL ES 1.x code to set my projection coordinates.
glMatrixMode(GL_PROJECTION);
float width = 320;
float height = 480;
glOrthof(0.0, // Left
1.0, // Right
height / width, // Bottom
0.0, // Top
-1.0, // Near
1.0); // Far
glMatrixMode(GL_MODELVIEW);
What is the equivalent method to setup this in OpenGL ES 2.0 ?
What projection matrix should I pass to the vertex shader ?
I have tried following function to create the matrix but its not working:
void SetOrtho (Matrix4x4& m, float left, float right, float bottom, float top, float near,
float far)
{
const float tx = - (right + left)/(right - left);
const float ty = - (top + bottom)/(top - bottom);
const float tz = - (far + near)/(far - near);
m.m[0] = 2.0f/(right-left);
m.m[1] = 0;
m.m[2] = 0;
m.m[3] = tx;
m.m[4] = 0;
m.m[5] = 2.0f/(top-bottom);
m.m[6] = 0;
m.m[7] = ty;
m.m[8] = 0;
m.m[9] = 0;
m.m[10] = -2.0/(far-near);
m.m[11] = tz;
m.m[12] = 0;
m.m[13] = 0;
m.m[14] = 0;
m.m[15] = 1;
}
Vertex Shader :
uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 v_color;
void main()
{
gl_Position = u_mvpMatrix * a_position;
v_color = a_color;
}
Client Code (parameters to the vertex shader):
float min = 0.0f;
float max = 1.0f;
const GLfloat squareVertices[] = {
min, min,
min, max,
max, min,
max, max
};
const GLfloat squareColors[] = {
1, 1, 0, 1,
0, 1, 1, 1,
0, 0, 0, 1,
1, 0, 1, 1,
};
Matrix4x4 proj;
SetOrtho(proj, 0.0f, 1.0f, 480.0/320.0, 0.0f, -1.0f, 1.0f );
The output i am getting in the iPhone simulator:

Your transcription of the glOrtho formula looks correct.
Your Matrix4x4 class is custom, but is it possible that m.m ends up being loaded directly as a glUniformMatrix4fv? If so check that you're setting the transpose flag as GL_TRUE, since you're loading data in row major format and OpenGL expects column major (ie, standard rules are that index [0] is the top of the first column, [3] is at the bottom of the first column, [4] is at the top of the second column, etc).
It's possibly also worth checking that —— assuming you've directly replicated the old world matrix stacks — you're applying modelview and projection in the correct order in your vertex shader or else compositing them correctly on the CPU, whichever way around you're doing it.

Related

What is this called and how to achieve! Visuals in processing

Hey does anyone know how to achieve this effect using processing or what this is called?
I have been trying to use the wave gradient example in the processing library and implementing Perlin noise but I can not get close to the gif quality.
I know the artist used processing but can not figure out how!
Link to gif:
https://giphy.com/gifs/processing-jodeus-QInYLzY33wMwM
The effect is reminescent of Op Art (optical illusion art): I recommend reading/learning more about this fascinating genre and artists like:
Bridget Riley
(Bridget Riley, Intake, 1964)
(Bridget Riley, Hesistate, 1964,
Copyright: (c) Bridget Riley 2018. All rights reserved. / Photo (c) Tate)
Victor Vasarely
(Victor Vasarely, Zebra Couple)
(Victor Vasarely, VegaII)
Frank Stella
(Frank Stella, Untitled 1965, Image curtesy of Art Gallery NSW)
and more
You notice this waves are reminiscent/heavily inspired by Bridget Riley's work.
I also recommend checking out San Charoenchai;s album visualiser for Beach House - 7
As mentioned in my comment: you should post your attempt.
Waves and perlin noise could work for sure.
There are many ways to achieve a similar look.
Here's tweaked version of Daniel Shiffman's Noise Wave example:
int numWaves = 24;
float[] yoff = new float[numWaves]; // 2nd dimension of perlin noise
float[] yoffIncrements = new float[numWaves];
void setup() {
size(640, 360);
noStroke();
for(int i = 0 ; i < numWaves; i++){
yoffIncrements[i] = map(i, 0, numWaves - 1, 0.01, 0.03);
}
}
void draw() {
background(0);
float waveHeight = height / numWaves;
for(int i = 0 ; i < numWaves; i++){
float waveY = i * waveHeight;
fill(i % 2 == 0 ? color(255) : color(0));
// We are going to draw a polygon out of the wave points
beginShape();
float xoff = 0; // Option #1: 2D Noise
// float xoff = yoff; // Option #2: 1D Noise
// Iterate over horizontal pixels
for (float x = 0; x <= width + 30; x += 20) {
// Calculate a y value according to noise, map to
float y = map(noise(xoff, yoff[i]), 0, 1, waveY , waveY + (waveHeight * 3)); // Option #1: 2D Noise
// float y = map(noise(xoff), 0, 1, 200,300); // Option #2: 1D Noise
// Set the vertex
vertex(x, y);
// Increment x dimension for noise
xoff += 0.05;
}
// increment y dimension for noise
yoff[i] += yoffIncrements[i];
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
}
}
Notice the quality of the noise wave in comparison to the image you're trying to emulate: there is a constant rhythm to it. To me that is a hint that it's using cycling sine waves changing phase and amplitude (potentially even adding waves together).
I've written an extensive answer on animating sine waves here
(Reuben Margolin's kinectic sculpture system demo)
From your question it sounds like you would be comfortable implementing a sine wave animation. It it helps, here's an example of adding two waves together:
void setup(){
size(600,600);
noStroke();
}
void draw(){
background(0);
// how many waves per sketch height
int heightDivisions = 30;
// split the sketch height into equal height sections
float heightDivisionSize = (float)height / heightDivisions;
// for each height division
for(int j = 0 ; j < heightDivisions; j++){
// use % 2 to alternate between black and white
// see https://processing.org/reference/modulo.html and
// https://processing.org/reference/conditional.html for more
fill(j % 2 == 0 ? color(255) : color(0));
// offset drawing on Y axis
translate(0,(j * heightDivisionSize));
// start a wave shape
beginShape();
// first vertex is at the top left corner
vertex(0,height);
// how many horizontal (per wave) divisions ?
int widthDivisions = 12;
// equally space the points on the wave horizontally
float widthDivsionSize = (float)width / widthDivisions;
// for each point on the wave
for(int i = 0; i <= widthDivisions; i++){
// calculate different phases
// play with arithmetic operators to make interesting wave additions
float phase1 = (frameCount * 0.01) + ((i * j) * 0.025);
float phase2 = (frameCount * 0.05) + ((i + j) * 0.25);
// calculate vertex x position
float x = widthDivsionSize * i;
// multiple sine waves
// (can use cos() and use other ratios too
// 150 in this case is the wave amplitude (e.g. from -150 to + 150)
float y = ((sin(phase1) * sin(phase2) * 150));
// draw calculated vertex
vertex(x,y);
}
// last vertex is at bottom right corner
vertex(width,height);
// finish the shape
endShape();
}
}
The result:
Minor note on performance: this could be implemented more efficiently using PShape, however I recommend playing with the maths/geometry to find the form you're after, then as a last step think of optimizing it.
My intention is not to show you how to create an exact replica, but to show there's more to Op Art than an effect and hopefully inspire you to explore other methods of achieving something similar in the hope that you will discover your own methods and outcomes: something new and of your own through fun happy accidents.
In terms of other techniques/avenues to explore:
displacement maps:
Using an alternating black/white straight bars texture on wavy 3D geometry
using shaders:
Shaders are a huge topic on their own, but it's worth noting:
There's a very good Processing Shader Tutorial
You might be able to explore frament shaders on shadertoy, tweak the code in browser then make slight changes so you can run them in Processing.
Here are a few quick examples:
https://www.shadertoy.com/view/Wts3DB
tweaked for black/white waves in Processing as shader-Wts3DB.frag
// https://www.shadertoy.com/view/Wts3DB
uniform vec2 iResolution;
uniform float iTime;
#define COUNT 6.
#define COL_BLACK vec3(23,32,38) / 255.0
#define SF 1./min(iResolution.x,iResolution.y)
#define SS(l,s) smoothstep(SF,-SF,l-s)
#define hue(h) clamp( abs( fract(h + vec4(3,2,1,0)/3.) * 6. - 3.) -1. , 0., 1.)
// Original noise code from https://www.shadertoy.com/view/4sc3z2
#define MOD3 vec3(.1031,.11369,.13787)
vec3 hash33(vec3 p3)
{
p3 = fract(p3 * MOD3);
p3 += dot(p3, p3.yxz+19.19);
return -1.0 + 2.0 * fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}
float simplex_noise(vec3 p)
{
const float K1 = 0.333333333;
const float K2 = 0.166666667;
vec3 i = floor(p + (p.x + p.y + p.z) * K1);
vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);
vec3 e = step(vec3(0.0), d0 - d0.yzx);
vec3 i1 = e * (1.0 - e.zxy);
vec3 i2 = 1.0 - e.zxy * (1.0 - e);
vec3 d1 = d0 - (i1 - 1.0 * K2);
vec3 d2 = d0 - (i2 - 2.0 * K2);
vec3 d3 = d0 - (1.0 - 3.0 * K2);
vec4 h = max(0.6 - vec4(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), 0.0);
vec4 n = h * h * h * h * vec4(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.0)));
return dot(vec4(31.316), n);
}
void mainImage( vec4 fragColor, vec2 fragCoord )
{
}
void main(void) {
//vec2 uv = vec2(gl_FragColor.x / iResolution.y, gl_FragColor.y / iResolution.y);
vec2 uv = gl_FragCoord.xy / iResolution.y;
float m = 0.;
float t = iTime *.5;
vec3 col;
for(float i=COUNT; i>=0.; i-=1.){
float edge = simplex_noise(vec3(uv * vec2(2., 0.) + vec2(0, t + i*.15), 3.))*.2 + (.95/COUNT)*i;
float mi = SS(edge, uv.y) - SS(edge + .095, uv.y);
m += mi;
if(mi > 0.){
col = vec3(1.0);
}
}
col = mix(COL_BLACK, col, m);
gl_FragColor = vec4(col,1.0);
// mainImage(gl_FragColor,gl_FragCoord);
}
loaded in Processing as:
PShader shader;
void setup(){
size(300,300,P2D);
noStroke();
shader = loadShader("shader-Wts3DB.frag");
shader.set("iResolution",(float)width, float(height));
}
void draw(){
background(0);
shader.set("iTime",frameCount * 0.05);
shader(shader);
rect(0,0,width,height);
}
https://www.shadertoy.com/view/MtsXzl
tweaked as shader-MtsXzl.frag
//https://www.shadertoy.com/view/MtsXzl
#define SHOW_GRID 1
const float c_scale = 0.5;
const float c_rate = 2.0;
#define FLT_MAX 3.402823466e+38
uniform vec3 iMouse;
uniform vec2 iResolution;
uniform float iTime;
//=======================================================================================
float CubicHermite (float A, float B, float C, float D, float t)
{
float t2 = t*t;
float t3 = t*t*t;
float a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0;
float b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0;
float c = -A/2.0 + C/2.0;
float d = B;
return a*t3 + b*t2 + c*t + d;
}
//=======================================================================================
float hash(float n) {
return fract(sin(n) * 43758.5453123);
}
//=======================================================================================
float GetHeightAtTile(vec2 T)
{
float rate = hash(hash(T.x) * hash(T.y))*0.5+0.5;
return (sin(iTime*rate*c_rate) * 0.5 + 0.5) * c_scale;
}
//=======================================================================================
float HeightAtPos(vec2 P)
{
vec2 tile = floor(P);
P = fract(P);
float CP0X = CubicHermite(
GetHeightAtTile(tile + vec2(-1.0,-1.0)),
GetHeightAtTile(tile + vec2(-1.0, 0.0)),
GetHeightAtTile(tile + vec2(-1.0, 1.0)),
GetHeightAtTile(tile + vec2(-1.0, 2.0)),
P.y
);
float CP1X = CubicHermite(
GetHeightAtTile(tile + vec2( 0.0,-1.0)),
GetHeightAtTile(tile + vec2( 0.0, 0.0)),
GetHeightAtTile(tile + vec2( 0.0, 1.0)),
GetHeightAtTile(tile + vec2( 0.0, 2.0)),
P.y
);
float CP2X = CubicHermite(
GetHeightAtTile(tile + vec2( 1.0,-1.0)),
GetHeightAtTile(tile + vec2( 1.0, 0.0)),
GetHeightAtTile(tile + vec2( 1.0, 1.0)),
GetHeightAtTile(tile + vec2( 1.0, 2.0)),
P.y
);
float CP3X = CubicHermite(
GetHeightAtTile(tile + vec2( 2.0,-1.0)),
GetHeightAtTile(tile + vec2( 2.0, 0.0)),
GetHeightAtTile(tile + vec2( 2.0, 1.0)),
GetHeightAtTile(tile + vec2( 2.0, 2.0)),
P.y
);
return CubicHermite(CP0X, CP1X, CP2X, CP3X, P.x);
}
//=======================================================================================
vec3 NormalAtPos( vec2 p )
{
float eps = 0.01;
vec3 n = vec3( HeightAtPos(vec2(p.x-eps,p.y)) - HeightAtPos(vec2(p.x+eps,p.y)),
2.0*eps,
HeightAtPos(vec2(p.x,p.y-eps)) - HeightAtPos(vec2(p.x,p.y+eps)));
return normalize( n );
}
//=======================================================================================
float RayIntersectSphere (vec4 sphere, in vec3 rayPos, in vec3 rayDir)
{
//get the vector from the center of this circle to where the ray begins.
vec3 m = rayPos - sphere.xyz;
//get the dot product of the above vector and the ray's vector
float b = dot(m, rayDir);
float c = dot(m, m) - sphere.w * sphere.w;
//exit if r's origin outside s (c > 0) and r pointing away from s (b > 0)
if(c > 0.0 && b > 0.0)
return -1.0;
//calculate discriminant
float discr = b * b - c;
//a negative discriminant corresponds to ray missing sphere
if(discr < 0.0)
return -1.0;
//ray now found to intersect sphere, compute smallest t value of intersection
float collisionTime = -b - sqrt(discr);
//if t is negative, ray started inside sphere so clamp t to zero and remember that we hit from the inside
if(collisionTime < 0.0)
collisionTime = -b + sqrt(discr);
return collisionTime;
}
//=======================================================================================
vec3 DiffuseColor (in vec3 pos)
{
#if SHOW_GRID
pos = mod(floor(pos),2.0);
return vec3(mod(pos.x, 2.0) < 1.0 ? 1.0 : 0.0);
#else
return vec3(0.1, 0.8, 0.9);
#endif
}
//=======================================================================================
vec3 ShadePoint (in vec3 pos, in vec3 rayDir, float time, bool fromUnderneath)
{
vec3 diffuseColor = DiffuseColor(pos);
vec3 reverseLightDir = normalize(vec3(1.0,1.0,-1.0));
vec3 lightColor = vec3(1.0);
vec3 ambientColor = vec3(0.05);
vec3 normal = NormalAtPos(pos.xz);
normal *= fromUnderneath ? -1.0 : 1.0;
// diffuse
vec3 color = diffuseColor;
float dp = dot(normal, reverseLightDir);
if(dp > 0.0)
color += (diffuseColor * lightColor);
return color;
}
//=======================================================================================
vec3 HandleRay (in vec3 rayPos, in vec3 rayDir, in vec3 pixelColor, out float hitTime)
{
float time = 0.0;
float lastHeight = 0.0;
float lastY = 0.0;
float height;
bool hitFound = false;
hitTime = FLT_MAX;
bool fromUnderneath = false;
vec2 timeMinMax = vec2(0.0, 20.0);
time = timeMinMax.x;
const int c_numIters = 100;
float deltaT = (timeMinMax.y - timeMinMax.x) / float(c_numIters);
vec3 pos = rayPos + rayDir * time;
float firstSign = sign(pos.y - HeightAtPos(pos.xz));
for (int index = 0; index < c_numIters; ++index)
{
pos = rayPos + rayDir * time;
height = HeightAtPos(pos.xz);
if (sign(pos.y - height) * firstSign < 0.0)
{
fromUnderneath = firstSign < 0.0;
hitFound = true;
break;
}
time += deltaT;
lastHeight = height;
lastY = pos.y;
}
if (hitFound) {
time = time - deltaT + deltaT*(lastHeight-lastY)/(pos.y-lastY-height+lastHeight);
pos = rayPos + rayDir * time;
pixelColor = ShadePoint(pos, rayDir, time, fromUnderneath);
hitTime = time;
}
return pixelColor;
}
//=======================================================================================
void main()
{
// scrolling camera
vec3 cameraOffset = vec3(iTime, 0.5, iTime);
//----- camera
vec2 mouse = iMouse.xy / iResolution.xy;
vec3 cameraAt = vec3(0.5,0.5,0.5) + cameraOffset;
float angleX = iMouse.z > 0.0 ? 6.28 * mouse.x : 3.14 + iTime * 0.25;
float angleY = iMouse.z > 0.0 ? (mouse.y * 6.28) - 0.4 : 0.5;
vec3 cameraPos = (vec3(sin(angleX)*cos(angleY), sin(angleY), cos(angleX)*cos(angleY))) * 5.0;
// float angleX = 0.8;
// float angleY = 0.8;
// vec3 cameraPos = vec3(0.0,0.0,0.0);
cameraPos += vec3(0.5,0.5,0.5) + cameraOffset;
vec3 cameraFwd = normalize(cameraAt - cameraPos);
vec3 cameraLeft = normalize(cross(normalize(cameraAt - cameraPos), vec3(0.0,sign(cos(angleY)),0.0)));
vec3 cameraUp = normalize(cross(cameraLeft, cameraFwd));
float cameraViewWidth = 6.0;
float cameraViewHeight = cameraViewWidth * iResolution.y / iResolution.x;
float cameraDistance = 6.0; // intuitively backwards!
// Objects
vec2 rawPercent = (gl_FragCoord.xy / iResolution.xy);
vec2 percent = rawPercent - vec2(0.5,0.5);
vec3 rayTarget = (cameraFwd * vec3(cameraDistance,cameraDistance,cameraDistance))
- (cameraLeft * percent.x * cameraViewWidth)
+ (cameraUp * percent.y * cameraViewHeight);
vec3 rayDir = normalize(rayTarget);
float hitTime = FLT_MAX;
vec3 pixelColor = vec3(1.0, 1.0, 1.0);
pixelColor = HandleRay(cameraPos, rayDir, pixelColor, hitTime);
gl_FragColor = vec4(clamp(pixelColor,0.0,1.0), 1.0);
}
and the mouse interactive Processing sketch:
PShader shader;
void setup(){
size(300,300,P2D);
noStroke();
shader = loadShader("shader-MtsXzl.frag");
shader.set("iResolution",(float)width, float(height));
}
void draw(){
background(0);
shader.set("iTime",frameCount * 0.05);
shader.set("iMouse",(float)mouseX , (float)mouseY, mousePressed ? 1.0 : 0.0);
shader(shader);
rect(0,0,width,height);
}
Shadertoy is great way to play/learn: have fun !
Update
Here's a quick test tweaking Daniel Shiffman's 3D Terrain Generation example to add a stripped texture and basic sine waves instead of perlin noise:
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/IKB1hWWedMk
int cols, rows;
int scl = 20;
int w = 2000;
int h = 1600;
float flying = 0;
float[][] terrain;
PImage texture;
void setup() {
size(600, 600, P3D);
textureMode(NORMAL);
noStroke();
cols = w / scl;
rows = h/ scl;
terrain = new float[cols][rows];
texture = getBarsTexture(512,512,96);
}
void draw() {
flying -= 0.1;
float yoff = flying;
for (int y = 0; y < rows; y++) {
float xoff = 0;
for (int x = 0; x < cols; x++) {
//terrain[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100);
terrain[x][y] = map(sin(xoff) * sin(yoff), 0, 1, -60, 60);
xoff += 0.2;
}
yoff += 0.2;
}
background(0);
translate(width/2, height/2+50);
rotateX(PI/9);
translate(-w/2, -h/2);
for (int y = 0; y < rows-1; y++) {
beginShape(TRIANGLE_STRIP);
texture(texture);
for (int x = 0; x < cols; x++) {
float u0 = map(x,0,cols-1,0.0,1.0);
float u1 = map(x+1,0,cols-1,0.0,1.0);
float v0 = map(y,0,rows-1,0.0,1.0);
float v1 = map(y+1,0,rows-1,0.0,1.0);
vertex(x*scl, y*scl, terrain[x][y], u0, v0);
vertex(x*scl, (y+1)*scl, terrain[x][y+1], u1, v1);
}
endShape();
}
}
PGraphics getBarsTexture(int textureWidth, int textureHeight, int numBars){
PGraphics texture = createGraphics(textureWidth, textureHeight);
int moduleSide = textureWidth / numBars;
texture.beginDraw();
texture.background(0);
texture.noStroke();
for(int i = 0; i < numBars; i+= 2){
texture.rect(0, i * moduleSide, textureWidth, moduleSide);
}
texture.endDraw();
return texture;
}

Optimize WebGL shader?

I wrote the following shader to render a pattern with a bunch of concentric circles. Eventually I want to have each rotating sphere be a light emitter to create something along these lines.
Of course right now I'm just doing the most basic part to render the different objects.
Unfortunately the shader is incredibly slow (16fps full screen on a high-end macbook). I'm pretty sure this is due to the numerous for loops and branching that I have in the shader. I'm wondering how I can pull off the geometry I'm trying to achieve in a more performance optimized way:
EDIT: you can run the shader here: https://www.shadertoy.com/view/lssyRH
One obvious optimization I am missing is that currently all the fragments are checked against the entire 24 surrounding circles. It would be pretty quick and easy to just discard these checks entirely by checking if the fragment intersects the outer bounds of the diagram. I guess I'm just trying to get a handle on how the best practice is of doing something like this.
#define N 10
#define M 5
#define K 24
#define M_PI 3.1415926535897932384626433832795
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
float aspectRatio = iResolution.x / iResolution.y;
float h = 1.0;
float w = aspectRatio;
vec2 uv = vec2(fragCoord.x / iResolution.x * aspectRatio, fragCoord.y / iResolution.y);
float radius = 0.01;
float orbitR = 0.02;
float orbiterRadius = 0.005;
float centerRadius = 0.002;
float encloseR = 2.0 * orbitR;
float encloserRadius = 0.002;
float spacingX = (w / (float(N) + 1.0));
float spacingY = h / (float(M) + 1.0);
float x = 0.0;
float y = 0.0;
vec4 totalLight = vec4(0.0, 0.0, 0.0, 1.0);
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// compute the center of the diagram
vec2 center = vec2(spacingX * (float(i) + 1.0), spacingY * (float(j) + 1.0));
x = center.x + orbitR * cos(iGlobalTime);
y = center.y + orbitR * sin(iGlobalTime);
vec2 bulb = vec2(x,y);
if (length(uv - center) < centerRadius) {
// frag intersects white center marker
fragColor = vec4(1.0);
return;
} else if (length(uv - bulb) < radius) {
// intersects rotating "light"
fragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);
return;
} else {
// intersects one of the enclosing 24 cylinders
for(int k = 0; k < K; k++) {
float theta = M_PI * 2.0 * float(k)/ float(K);
x = center.x + cos(theta) * encloseR;
y = center.y + sin(theta) * encloseR;
vec2 encloser = vec2(x,y);
if (length(uv - encloser) < encloserRadius) {
fragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);
return;
}
}
}
}
}
}
Keeping in mind that you want to optimize the fragment shader, and only the fragment shader:
Move the sin(iGlobalTime) and cos(iGlobalTime) out of the loops, these remain static over the whole draw call so no need to recalculate them every loop iteration.
GPUs employ vectorized instruction sets (SIMD) where possible, take advantage of that. You're wasting lots of cycles by doing multiple scalar ops where you could use a single vector instruction(see annotated code)
[Three years wiser me here: I'm not really sure if this statement is true in regards to how modern GPUs process the instructions, however it certainly does help readability and maybe even give a hint or two to the compiler]
Do your radius checks squared, save that sqrt(length) for when you really need it
Replace float casts of constants(your loop limits) with a float constant(intelligent shader compilers will already do this, not something to count on though)
Don't have undefined behavior in your shader(not writing to gl_FragColor)
Here is an optimized and annotated version of your shader(still containing that undefined behavior, just like the one you provided). Annotation is in the form of:
// annotation
// old code, if any
new code
#define N 10
// define float constant N
#define fN 10.
#define M 5
// define float constant M
#define fM 5.
#define K 24
// define float constant K
#define fK 24.
#define M_PI 3.1415926535897932384626433832795
// predefine 2 times PI
#define M_PI2 6.28318531
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
float aspectRatio = iResolution.x / iResolution.y;
// we dont need these separate
// float h = 1.0;
// float w = aspectRatio;
// use vector ops(2 divs 1 mul => 1 div 1 mul)
// vec2 uv = vec2(fragCoord.x / iResolution.x * aspectRatio, fragCoord.y / iResolution.y);
vec2 uv = fragCoord.xy / iResolution.xy;
uv.x *= aspectRatio;
// most of the following declarations should be predefined or marked as "const"...
float radius = 0.01;
// precalc squared radius
float radius2 = radius*radius;
float orbitR = 0.02;
float orbiterRadius = 0.005;
float centerRadius = 0.002;
// precalc squared center radius
float centerRadius2 = centerRadius * centerRadius;
float encloseR = 2.0 * orbitR;
float encloserRadius = 0.002;
// precalc squared encloser radius
float encloserRadius2 = encloserRadius * encloserRadius;
// Use float constants and vector ops here(2 casts 2 adds 2 divs => 1 add 1 div)
// float spacingX = w / (float(N) + 1.0);
// float spacingY = h / (float(M) + 1.0);
vec2 spacing = vec2(aspectRatio, 1.0) / (vec2(fN, fM)+1.);
// calc sin and cos of global time
// saves N*M(sin,cos,2 muls)
vec2 stct = vec2(sin(iGlobalTime), cos(iGlobalTime));
vec2 orbit = orbitR * stct;
// not needed anymore
// float x = 0.0;
// float y = 0.0;
// was never used
// vec4 totalLight = vec4(0.0, 0.0, 0.0, 1.0);
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// compute the center of the diagram
// Use vector ops
// vec2 center = vec2(spacingX * (float(i) + 1.0), spacingY * (float(j) + 1.0));
vec2 center = spacing * (vec2(i,j)+1.0);
// Again use vector opts, use precalced time trig(orbit = orbitR * stct)
// x = center.x + orbitR * cos(iGlobalTime);
// y = center.y + orbitR * sin(iGlobalTime);
// vec2 bulb = vec2(x,y);
vec2 bulb = center + orbit;
// calculate offsets
vec2 centerOffset = uv - center;
vec2 bulbOffset = uv - bulb;
// use squared length check
// if (length(uv - center) < centerRadius) {
if (dot(centerOffset, centerOffset) < centerRadius2) {
// frag intersects white center marker
fragColor = vec4(1.0);
return;
// use squared length check
// } else if (length(uv - bulb) < radius) {
} else if (dot(bulbOffset, bulbOffset) < radius2) {
// Use precalced sin global time in stct.x
// intersects rotating "light"
fragColor = vec4(uv,0.5+0.5*stct.x,1.0);
return;
} else {
// intersects one of the enclosing 24 cylinders
for(int k = 0; k < K; k++) {
// use predefined 2*PI and float K
float theta = M_PI2 * float(k) / fK;
// Use vector ops(2 muls 2 adds => 1 mul 1 add)
// x = center.x + cos(theta) * encloseR;
// y = center.y + sin(theta) * encloseR;
// vec2 encloser = vec2(x,y);
vec2 encloseOffset = uv - (center + vec2(cos(theta),sin(theta)) * encloseR);
if (dot(encloseOffset,encloseOffset) < encloserRadius2) {
fragColor = vec4(uv,0.5+0.5*stct.x,1.0);
return;
}
}
}
}
}
}
I did a little more thinking ... I realized the best way to optimize it is to actually change the logic so that before doing intersection tests on the small circles it checks the bounds of the group of circles. This got it to run at 60fps:
Example here:
https://www.shadertoy.com/view/lssyRH

Sprite Kit Shader Uniforms Ignored

The shader that I'm using relies upon the position of the tiles in my game. I haven't found anything on using attribute variables with SKShader objects, so I went with updating the uniform variables. But it would seem that the shader won't communicate with the variables, especially once their values have been updated and changed. I am trying to make a basic lighting effect, but I can't get anything out of the shader at all. Any help? My code for the shader and for the Objective C classes are below.
Shader
uniform float midX, midY;
uniform float posX;
uniform float posY;
void main()
{
vec4 temp = SKDefaultShading(); // get the default shading
float lightRad = 200.0; // Light radius
float dist = distance(vec2(posX, posY), vec2(midX, midY)); // location of the light on the screen
vec4 color = vec4(1.0, 0, 0.0, (float)(dist / lightRad)); // creates an alpha gradient for the light. (falloff)
if (dist < lightRad) // only applies the light color if the distance from the light to the tile is smaller than the radius of the light
{
gl_FragColor = temp * color; // applies the color
}
else // otherwise, do nothing
{
gl_FragColor = temp;
}
}
Code
- (void) loadShaders
{
SKUniform* posX = [SKUniform uniformWithName:#"posX" float: 0.0f]; // adds the x position (with a placeholder value)
SKUniform* posY = [SKUniform uniformWithName:#"posY" float: 0.0f]; // adds the y position (with a placeholder value)
[_shader addUniform:posX];
[_shader addUniform:posY];
}
-(void)update:(CFTimeInterval)currentTime
{
for (int i = 0; i < _array.count; i++) // Loop through all tiles
{
float x = ((i % 100) - 13.5f) * 15.0f; // Calculate x pos of the tile
float y = ((1 - (i / 100)) + 6.5f) * 15.0f; // Calculate y pos of the tile
SKUniform* uniX = [[_tMap getShader] uniformNamed:#"posX"]; // get the uniform with the name posX
uniX.floatValue = x; // set the value of that uniform
SKUniform* uniY = [[_tMap getShader] uniformNamed:#"posY"]; // get the uniform with the name posY
uniY.floatValue = y; // set the value of that uniform
}
}
I'm fairly new to sprite kit, and I'm also new to GLSL.

Artifacts from linear filtering a floating point texture in the fragment shader

I'm using the following code taken from this tutorial to perform linear filtering on a floating point texture in my fragment shader in WebGL:
float fHeight = 512.0;
float fWidth = 1024.0;
float texelSizeX = 1.0/fWidth;
float texelSizeY = 1.0/fHeight;
float tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i )
{
float p0q0 = texture2D(textureSampler_i, texCoord_i)[0];
float p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0))[0];
float p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY))[0];
float p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY))[0];
float a = fract( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
// Fraction near to valid data.
float pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
float pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.
float b = fract( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}
On an Nvidia GPU this looks fine, but on two other computers with an Intel integrated GPU it looks like this:
There are lighter or darker lines appearing that shouldn't be there. They become visible if you zoom in, and tend to get more frequent the more you zoom. When zooming in very closely, they appear at the edge of every texel of the texture I'm filtering. I tried changing the precision statement in the fragment shader, but this didn't fix it.
The built-in linear filtering works on both GPUs, but I still need the manual filtering as a fallback for GPUs that don't support linear filtering on floating point textures with WebGL.
The Intel GPUs are from a desktop Core i5-4460 and a notebook with an Intel HD 5500 GPU. For all precisions of floating point values I get a rangeMin and rangeMax of 127 and a precision of 23 from getShaderPrecisionFormat.
Any idea on what causes these artifacts and how I can work around it?
Edit:
By experimenting a bit more I found that reducing the texel size variable in the fragment shader removes these artifacts:
float texelSizeX = 1.0/fWidth*0.998;
float texelSizeY = 1.0/fHeight*0.998;
Multiplying by 0.999 isn't enough, but multiplying the texel size by 0.998 removes the artifacts.
This is obviously not a satisfying fix, I still don't know what causes it and I probably caused artifacts on other GPUs or drivers now. So I'm still interested in figuring out what the actual issue is here.
It's not clear to me what the code is trying to do. It's not reproducing the GPU's bilinear because that would be using pixels centered around the texcoord.
In other words, as implemented
vec4 c = tex2DBiLinear(someSampler, someTexcoord);
is NOT equivilent to LINEAR
vec4 c = texture2D(someSampler, someTexcoord);
texture2D looks at pixels someTexcoord +/- texelSize * .5 where as tex2DBiLinear is looking at pixels someTexcoord and someTexcoord + texelSize
You haven't given enough code to repo your issue. I'm guessing the size of the source texture is 512x1024 but since you didn't post that code I have no idea if your source texture matches the defined size. You also didn't post what size your target is. The top image you posted is 471x488. Was that your target size? You also didn't post your code for what texture coordinates you're using and the code that manipulates them.
Guessing that your source is 512x1024, your target is 471x488 I can't repo your issue.
const fs = `
precision highp float;
uniform sampler2D tex;
varying vec2 v_texcoord;
float tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i )
{
float fHeight = 1024.0;
float fWidth = 512.0;
float texelSizeX = 1.0/fWidth;
float texelSizeY = 1.0/fHeight;
float p0q0 = texture2D(textureSampler_i, texCoord_i)[0];
float p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0))[0];
float p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY))[0];
float p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY))[0];
float a = fract( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
// Fraction near to valid data.
float pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
float pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.
float b = fract( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}
void main() {
gl_FragColor = vec4(tex2DBiLinear(tex, v_texcoord), 0, 0, 1);
}
`;
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
const gl = document.querySelector('canvas').getContext('webgl');
// compile shaders, link programs, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-1, -1,
1, -1,
-1, 1,
1, 1,
],
},
texcoord: [
0, 0,
1, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2,
2, 1, 3,
],
});
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 512;
ctx.canvas.height = 1024;
const gradient = ctx.createRadialGradient(256, 512, 0, 256, 512, 700);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'cyan');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 512, 1024);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
minMag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
auto: false,
});
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas width="471" height="488"></canvas>
If you think the issue is related to floating point textures I can't repo there either
const fs = `
precision highp float;
uniform sampler2D tex;
varying vec2 v_texcoord;
float tex2DBiLinear( sampler2D textureSampler_i, vec2 texCoord_i )
{
float fHeight = 1024.0;
float fWidth = 512.0;
float texelSizeX = 1.0/fWidth;
float texelSizeY = 1.0/fHeight;
float p0q0 = texture2D(textureSampler_i, texCoord_i)[0];
float p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0))[0];
float p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY))[0];
float p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY))[0];
float a = fract( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
// Fraction near to valid data.
float pInterp_q0 = mix( p0q0, p1q0, a ); // Interpolates top row in X direction.
float pInterp_q1 = mix( p0q1, p1q1, a ); // Interpolates bottom row in X direction.
float b = fract( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
}
void main() {
gl_FragColor = vec4(tex2DBiLinear(tex, v_texcoord), 0, 0, 1);
}
`;
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('OES_texture_float');
if (!ext) { alert('need OES_texture_float'); }
// compile shaders, link programs, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-1, -1,
1, -1,
-1, 1,
1, 1,
],
},
texcoord: [
0, 0,
1, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2,
2, 1, 3,
],
});
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 512;
ctx.canvas.height = 1024;
const gradient = ctx.createRadialGradient(256, 512, 0, 256, 512, 700);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'cyan');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 512, 1024);
const tex = twgl.createTexture(gl, {
src: ctx.canvas,
type: gl.FLOAT,
minMag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
auto: false,
});
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
const e = gl.getExtension('WEBGL_debug_renderer_info');
if (e) {
console.log(gl.getParameter(e.UNMASKED_VENDOR_WEBGL));
console.log(gl.getParameter(e.UNMASKED_RENDERER_WEBGL));
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas width="471" height="488"></canvas>
If any of the values are off. If your source texture size doesn't match fWidth and fHeigth or if your texture coordinates are different or adjusted in some way then of course maybe I could repo. If any of those are different then I can imagine issues.
Tested in Intel Iris Pro and Intel HD Graphics 630. Also tested on an iPhone6+. Note that you need to make sure your fragment shader is running in precision highp float but that setting would likely only affect mobile GPUs.
We had almost identical issue that ocurred at specific zoom of texture. We found out that positions where artifacts appers can be detected with this conditions:
vec2 imagePosCenterity = fract(uv * imageSize);
if (abs(imagePosCenterity.x-0.5) < 0.001 || abs(imagePosCenterity.y-0.5) < 0.001) {}
Where imageSize is width and height of the texture.
Our solution looks like this:
vec4 texture2DLinear( sampler2D texSampler, vec2 uv) {
vec2 pixelOff = vec2(0.5,0.5)/imageSize;
vec2 imagePosCenterity = fract(uv * imageSize);
if (abs(imagePosCenterity.x-0.5) < 0.001 || abs(imagePosCenterity.y-0.5) < 0.001) {
pixelOff = pixelOff-vec2(0.00001,0.00001);
}
vec4 tl = texture2D(texSampler, uv + vec2(-pixelOff.x,-pixelOff.y));
vec4 tr = texture2D(texSampler, uv + vec2(pixelOff.x,-pixelOff.y));
vec4 bl = texture2D(texSampler, uv + vec2(-pixelOff.x,pixelOff.y));
vec4 br = texture2D(texSampler, uv + vec2(pixelOff.x,pixelOff.y));
vec2 f = fract( (uv.xy-pixelOff) * imageSize );
vec4 tA = mix( tl, tr, f.x );
vec4 tB = mix( bl, br, f.x );
return mix( tA, tB, f.y );
}
It is really dirty solution but it works. Changing texelSize as suggested above only moves artifacts to another positions. We are changing texelSize a little bit only on problematic positions.
Why we are using linear texture interpolation in GLSL shader? It is because we need to use 1 sample per pixel 16 bit per sample texture with broad set of compatibile devices. It is possible to do it only with OES_texture_half_float_linear extension. By our approach it is possible to solve it without using extension.

GLSL Vertex Shader gives wrong results if I do not mention gl_Vertex

I am writing a shader to draw lines with a width, as an alternative to glLineWidth, which doesn't work above 1.0 with ANGLE, and I'd like my lines to have the same thickness on Windows. I am running on desktop OpenGL for now, though.
The vertex shader source is as follows
attribute vec3 a_startPosition;
attribute vec3 a_endPosition;
attribute float a_choice;
attribute float a_dir;
uniform mat4 u_mvpMatrix;
uniform float u_width;
uniform vec2 u_viewDims;
void main()
{
vec4 start = u_mvpMatrix*vec4(a_startPosition, 1.0);
vec4 end = u_mvpMatrix*vec4(a_endPosition, 1.0);
//gl_Vertex;
vec2 slope = normalize(end.xy - start.xy);
slope = vec2(slope.y, -slope.x);
vec2 scale = u_width/u_viewDims;
if (a_choice == 0.0)
gl_Position = vec4(start.xy + a_dir*scale*slope.xy*start.w, start.zw);
else
gl_Position = vec4(end.xy + a_dir*scale*slope.xy*end.w, end.zw);
}
See that I have gl_Vertex, unused, commented out.
int width, height;
glfwGetFramebufferSize(m_window, &width, &height);
glUseProgram(m_shaders[Shader_WideLine]->id());
GLint shaderid = m_shaders[Shader_WideLine]->id();
GLint coloc = glGetUniformLocation(shaderid, "Color");
GLint dimloc = glGetUniformLocation(shaderid, "u_viewDims");
GLint widthloc = glGetUniformLocation(shaderid, "u_width");
GLint mvploc = glGetUniformLocation(shaderid, "u_mvpMatrix");
GLint modelviewloc = glGetUniformLocation(shaderid, "u_modelview");
GLint projloc = glGetUniformLocation(shaderid, "u_projection");
GLint dirloc = glGetAttribLocation(shaderid, "a_dir");
GLint startloc = glGetAttribLocation(shaderid, "a_startPosition");
GLint endloc = glGetAttribLocation(shaderid, "a_endPosition");
GLint chloc = glGetAttribLocation(shaderid, "a_choice");
//////////
//Set Uniforms
//////////
glUniform1f(widthloc, 10);
glUniform2f(dimloc, width, height);
glUniform4f(coloc, 0.101f, 0.558f, 0.109f, 1.f);
glm::mat4 modelview;
glm::mat4 projection;
glGetFloatv(GL_MODELVIEW_MATRIX, glm::value_ptr(modelview));
glGetFloatv(GL_PROJECTION_MATRIX, glm::value_ptr(projection));
glm::mat4 mvp = projection * modelview;
glUniformMatrix4fv(mvploc, 1, GL_FALSE, glm::value_ptr(mvp));
int numpts = 4;
GLfloat v[4][3] = {
{0,1,0},
{0,0,0},
{1,0,0},
{1,1,0}
};
//////////
// Draw (attributes)
//////////
glBegin( GL_TRIANGLE_STRIP );
glNormal3d(0.0, 0.0, 1.0);
for(int i=0; i<numpts-1; i++)
{
glVertexAttrib3fv(startloc, v[i]);
glVertexAttrib3fv(endloc, v[i+1]);
glVertexAttrib1f(chloc, 0);
glVertexAttrib1f(dirloc, -1.0f);
glVertex3d(0,0,0);
glVertexAttrib1f(dirloc, 1.0f);
glVertex3d(0,0,0);
glVertexAttrib1f(chloc, -1);
glVertexAttrib1f(dirloc, -1.0f);
glVertex3d(0,0,0);
glVertexAttrib1f(dirloc, 1.0f);
glVertex3d(0,0,0);
}
glEnd();
glUseProgram(0);
So I am trying to draw lines from (0,1,0) to (0,0,0) to (1,0,0) to (1,1,0) with a width of 10 pixels. In the following images is a wire cube 2x2x2 centered on the origin for reference.
When called as presented I get the unexpected result of this
If I uncomment gl_Vertex; in the shader, so that it is unused but referenced, I get this expected result.
What is the reason that this could happen?
gl_ModelViewProjectionMatrix is not a valid ES 2.0 vertex shader built-in variable.
You'll have to pass in your MVP via uniform.

Resources