I've been trying to use a fisheye shader from Shadertoy.
I've added my own frame resolution, and changed some keywords (texture -> texture2D, fragColor -> gl_FragColor) but that's it.
I don't really know why it doesn't work and how to debug it..
As a result I get a unicolor grey image.
Here's the code of my fragment shader :
precision mediump float;
uniform vec4 v_Color;
uniform sampler2D u_Texture;
varying vec2 v_TexCoordinate;
#define RESOLUTION_WIDTH 375.0
#define RESOLUTION_HEIGHT 211.0
#define POWER 2.0
void main() {
vec2 fragCoord = v_TexCoordinate;
vec2 iResolution = vec2(RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
vec2 p = fragCoord.xy / iResolution.x; // normalized coords with some cheat
float prop = iResolution.x / iResolution.y;
vec2 m = vec2(0.5, 0.5 / prop); // center coords
vec2 d = p - m; // vector from center to current fragment
float r = sqrt(dot(d, d)); // distance of pixel from center
float power = POWER;
float bind; // radius of 1:1 effect
if (power > 0.0)
bind = sqrt(dot(m, m)); // stick to corners
else {
if (prop < 1.0)
bind = m.x;
else
bind = m.y;
} // stick to borders
// Weird formulas
vec2 uv;
if (power > 0.0) // fisheye
uv = m + normalize(d) * tan(r * power) * bind / tan( bind * power);
else if (power < 0.0) // antifisheye
uv = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
else uv = p; // no effect for power = 1.0
vec3 col = texture2D(u_Texture, vec2(uv.x, -uv.y * prop)).xyz; // Second part of cheat
gl_FragColor = vec4(col, 1.0);
}
Here's my original shader to display an image that works perfectly :
precision mediump float;
uniform vec4 v_Color;
uniform sampler2D u_Texture;
varying vec2 v_TexCoordinate;
void main() {
// premultiplied alpha
vec4 texColor = texture2D(u_Texture, v_TexCoordinate);
// Scale the texture RGB by the vertex color
texColor.rgb *= v_Color.rgb;
// Scale the texture RGBA by the vertex alpha to reinstate premultiplication
gl_FragColor = texColor * v_Color.a;
}
Here's the link to the expected result on ShaderToy :
ShaderToy fisheye
Original result image :
With my shader :
With Rabbid76 solution :
With power = 1.1 :
With solution n2 and power = 10 (bigger image to see better) :
There's some background behind the text, don't pay attention to it ;)
In your shader code fragCoord is assumed to be a window coordinate, were the minimum is (0, 0) and the maximum is the width and height of the viewport. But in your code v_TexCoordinate is assigned to fragCoord. v_TexCoordinate is the texture corodiante in range [0, 1].
Use gl_FragCoord instead of v_TexCoordinate:
// vec2 fragCoord = v_TexCoordinate; <--- delete
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = fragCoord.xy / iResolution.x;
Or skip dividing by the window resolution:
vec2 fragCoord = v_TexCoordinate;
// vec2 p = fragCoord.xy / iResolution.x; <-- delete
vec2 p = fragCoord.xy * vec2(1.0, iResolution.y/iResolution.x);
If the aspect ratio correction is not needed, then it can be even done:
vec2 p = v_TexCoordinate.xy;
See the WebGL example, where I use your original shader code and applied the suggested changes:
(function loadscene() {
var gl, canvas, prog, bufObj = {};
var texture;
function render(deltaMS) {
texture.bound = texture.bound || texture.bind( 0 );
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
ShProg.Use( progDraw );
ShProg.SetF2( progDraw, "resolution", vp_size );
ShProg.SetI1( progDraw, "u_texture", 0 );
VertexBuffer.Draw( bufRect );
requestAnimationFrame(render);
}
function initScene() {
canvas = document.getElementById( "texture-canvas");
gl = canvas.getContext( "experimental-webgl" );
//gl = canvas.getContext( "webgl2" );
if ( !gl )
return;
progDraw = ShProg.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progDraw.inPos = gl.getAttribLocation( progDraw.progObj, "inPos" );
if ( progDraw.progObj == 0 )
return;
bufRect = VertexBuffer.Create(
[ { data : [ -1, -1, 1, -1, 1, 1, -1, 1 ], attrSize : 2, attrLoc : progDraw.inPos } ],
[ 0, 1, 2, 0, 2, 3 ] );
texture = new Texture( "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/supermario.jpg" );
texture.bound = false;
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
vp_size[0] = vp_size[1] = Math.min(vp_size[0], vp_size[1]);
//vp_size = [256, 256]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
var ShProg = {
Create: function (shaderList) {
var shaderObjs = [];
for (var i_sh = 0; i_sh < shaderList.length; ++i_sh) {
var shderObj = this.Compile(shaderList[i_sh].source, shaderList[i_sh].stage);
if (shderObj) shaderObjs.push(shderObj);
}
var prog = {}
prog.progObj = this.Link(shaderObjs)
if (prog.progObj) {
prog.attrInx = {};
var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
var name = gl.getActiveAttrib(prog.progObj, i_n).name;
prog.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
}
prog.uniLoc = {};
var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
var name = gl.getActiveUniform(prog.progObj, i_n).name;
prog.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
}
}
return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: function (source, shaderStage) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
var shaderObj = gl.createShader(shaderStage);
gl.shaderSource(shaderObj, source);
gl.compileShader(shaderObj);
var status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
if (!status) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : null;
},
Link: function (shaderObjs) {
var prog = gl.createProgram();
for (var i_sh = 0; i_sh < shaderObjs.length; ++i_sh)
gl.attachShader(prog, shaderObjs[i_sh]);
gl.linkProgram(prog);
status = gl.getProgramParameter(prog, gl.LINK_STATUS);
if ( !status ) alert(gl.getProgramInfoLog(prog));
return status ? prog : null;
} };
var VertexBuffer = {
Create: function(attribs, indices, type) {
var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
for (var i=0; i<attribs.length; ++i) {
buffer.buf.push(gl.createBuffer());
buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc, no_of: attribs[i].data.length/attribs[i].attrSize });
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
if ( buffer.inxLen > 0 ) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
return buffer;
},
Draw: function(bufObj) {
for (var i=0; i<bufObj.buf.length; ++i) {
gl.bindBuffer(gl.ARRAY_BUFFER, bufObj.buf[i]);
gl.vertexAttribPointer(bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray( bufObj.attr[i].loc);
}
if ( bufObj.inxLen > 0 ) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufObj.inx);
gl.drawElements(bufObj.primitive_type, bufObj.inxLen, gl.UNSIGNED_SHORT, 0);
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
else
gl.drawArrays(bufObj.primitive_type, 0, bufObj.attr[0].no_of );
for (var i=0; i<bufObj.buf.length; ++i)
gl.disableVertexAttribArray(bufObj.attr[i].loc);
gl.bindBuffer( gl.ARRAY_BUFFER, null );
} };
class Texture {
constructor( name, dflt ) {
let texture = this;
this.dflt = dflt || [128,128,128,255]
let image = { "cx": this.dflt.w || 1, "cy": this.dflt.h || 1, "plane": this.dflt.p || this.dflt };
this.size = [image.cx, image.cy];
this.dummyObj = Texture.createTexture2D( image, true )
this.image = new Image(64,64);
this.image.setAttribute('crossorigin', 'anonymous');
this.image.onload = function () {
let cx = 1 << 31 - Math.clz32(texture.image.naturalWidth);
if ( cx < texture.image.naturalWidth ) cx *= 2;
let cy = 1 << 31 - Math.clz32(texture.image.naturalHeight);
if ( cy < texture.image.naturalHeight ) cy *= 2;
var canvas = document.createElement( 'canvas' );
canvas.width = cx;
canvas.height = cy;
var context = canvas.getContext( '2d' );
context.drawImage( texture.image, 0, 0, canvas.width, canvas.height );
texture.textureObj = Texture.createTexture2D( canvas, true );
texture.size = [cx, cy];
}
this.image.src = name;
}
static createTexture2D( image, flipY ) {
let t = gl.createTexture();
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, t );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, flipY != undefined && flipY == true );
if ( image.cx && image.cy && image.plane )
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, image.cx, image.cy, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(image.plane) );
else
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
gl.bindTexture( gl.TEXTURE_2D, null );
return t;
}
bind( texUnit = 0 ) {
gl.activeTexture( gl.TEXTURE0 + texUnit );
if ( this.textureObj ) {
gl.bindTexture( gl.TEXTURE_2D, this.textureObj );
return true;
}
gl.bindTexture( gl.TEXTURE_2D, this.dummyObj );
return false;
}
};
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
void main()
{
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 resolution;
uniform sampler2D u_Texture;
#define RESOLUTION_WIDTH 375.0
#define RESOLUTION_HEIGHT 211.0
#define POWER 2.0
void main( void )
{
vec2 fragCoord = gl_FragCoord.xy;
vec2 iResolution = resolution;
//vec2 fragCoord = v_TexCoordinate;
//vec2 iResolution = vec2(RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
vec2 p = fragCoord.xy / iResolution.x; // normalized coords with some cheat
float prop = iResolution.x / iResolution.y;
vec2 m = vec2(0.5, 0.5 / prop); // center coords
vec2 d = p - m; // vector from center to current fragment
float r = sqrt(dot(d, d)); // distance of pixel from center
float power = POWER;
float bind; // radius of 1:1 effect
if (power > 0.0)
bind = sqrt(dot(m, m)); // stick to corners
else {
if (prop < 1.0)
bind = m.x;
else
bind = m.y;
} // stick to borders
// Weird formulas
vec2 uv;
if (power > 0.0) // fisheye
uv = m + normalize(d) * tan(r * power) * bind / tan( bind * power);
else if (power < 0.0) // antifisheye
uv = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
else uv = p; // no effect for power = 1.0
vec3 col = texture2D(u_Texture, vec2(uv.x, -uv.y * prop)).xyz; // Second part of cheat
gl_FragColor = vec4(col, 1.0);
}
</script>
<body>
<canvas id="texture-canvas" style="border: none"></canvas>
</body>
Related
I am creating a simple 2D web game that works with your typical tile map and sprites.
The twist is that I want smooth camera controls, both translation and scaling (zooming).
I tried using both the Canvas 2D API, and WebGL, and in both I simply cannot avoid the bleeding grid line artifacts, while also supporting zooming properly.
If it matters, all of my tiles are of size 1, and scaled to whatever size is needed, all of their coordinates are integers, and I am using a texture atlas.
Here's an example picture using my WebGL code, where the thin red/white lines are not wanted.
I remember writing sprite tile maps years ago with desktop GL, ironically using similar code (more or less equivalent to what I could do with WebGL 2), and it never had any of these issues.
I am considering to try DOM based elements next, but I fear it will not feel or look smooth.
One solution is to draw the tiles in the fragment shader
So you have your map, say a Uint32Array. Break it down into units of 4 bytes each. First 2 bytes are the tile ID, last byte is flags
As you walk across the quad for each pixel you lookup in the tilemap texture which tile it is, then you use that to compute UV coordinates to get pixels from that tile out of the texture of tiles. If your texture of tiles has gl.NEAREST sampling set then you'll never get any bleeding
Note that unlike traditional tilemaps the ids of each tile is the X,Y coordinate of the tile in the tile texture. In other words if your tile texture has 16x8 tiles across and you want your map to show the tile 7 over and 4 down then the id of that tile is 7,4 (first byte 7, second byte 4) where as in a traditional CPU based system the tile id would probably be 4*16+7 or 71 (the 71st tile). You could add code to the shader to do more traditional indexing but since the shader has to convert the id into 2d texture coords it just seemed easier to use 2d ids.
const vs = `
attribute vec4 position;
//attribute vec4 texcoord; - since position is a unit square just use it for texcoords
uniform mat4 u_matrix;
uniform mat4 u_texMatrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
// v_texcoord = (u_texMatrix * texccord).xy;
v_texcoord = (u_texMatrix * position).xy;
}
`;
const fs = `
precision highp float;
uniform sampler2D u_tilemap;
uniform sampler2D u_tiles;
uniform vec2 u_tilemapSize;
uniform vec2 u_tilesetSize;
varying vec2 v_texcoord;
void main() {
vec2 tilemapCoord = floor(v_texcoord);
vec2 texcoord = fract(v_texcoord);
vec2 tileFoo = fract((tilemapCoord + vec2(0.5, 0.5)) / u_tilemapSize);
vec4 tile = floor(texture2D(u_tilemap, tileFoo) * 256.0);
float flags = tile.w;
float xflip = step(128.0, flags);
flags = flags - xflip * 128.0;
float yflip = step(64.0, flags);
flags = flags - yflip * 64.0;
float xySwap = step(32.0, flags);
if (xflip > 0.0) {
texcoord = vec2(1.0 - texcoord.x, texcoord.y);
}
if (yflip > 0.0) {
texcoord = vec2(texcoord.x, 1.0 - texcoord.y);
}
if (xySwap > 0.0) {
texcoord = texcoord.yx;
}
vec2 tileCoord = (tile.xy + texcoord) / u_tilesetSize;
vec4 color = texture2D(u_tiles, tileCoord);
if (color.a <= 0.1) {
discard;
}
gl_FragColor = color;
}
`;
const tileWidth = 32;
const tileHeight = 32;
const tilesAcross = 8;
const tilesDown = 4;
const m4 = twgl.m4;
const gl = document.querySelector('#c').getContext('webgl');
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// gl.createBuffer, bindBuffer, bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
],
},
});
function r(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
// make some tiles
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = tileWidth * tilesAcross;
ctx.canvas.height = tileHeight * tilesDown;
ctx.font = "bold 24px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const f = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~';
for (let y = 0; y < tilesDown; ++y) {
for (let x = 0; x < tilesAcross; ++x) {
const color = `hsl(${r(360) | 0},${r(50,100)}%,50%)`;
ctx.fillStyle = color;
const tx = x * tileWidth;
const ty = y * tileHeight;
ctx.fillRect(tx, ty, tileWidth, tileHeight);
ctx.fillStyle = "#FFF";
ctx.fillText(f.substr(y * 8 + x, 1), tx + tileWidth * .5, ty + tileHeight * .5);
}
}
document.body.appendChild(ctx.canvas);
const tileTexture = twgl.createTexture(gl, {
src: ctx.canvas,
minMag: gl.NEAREST,
});
// make a tilemap
const mapWidth = 400;
const mapHeight = 300;
const tilemap = new Uint32Array(mapWidth * mapHeight);
const tilemapU8 = new Uint8Array(tilemap.buffer);
const totalTiles = tilesAcross * tilesDown;
for (let i = 0; i < tilemap.length; ++i) {
const off = i * 4;
// mostly tile 9
const tileId = r(10) < 1
? (r(totalTiles) | 0)
: 9;
tilemapU8[off + 0] = tileId % tilesAcross;
tilemapU8[off + 1] = tileId / tilesAcross | 0;
const xFlip = r(2) | 0;
const yFlip = r(2) | 0;
const xySwap = r(2) | 0;
tilemapU8[off + 3] =
(xFlip ? 128 : 0) |
(yFlip ? 64 : 0) |
(xySwap ? 32 : 0) ;
}
const mapTexture = twgl.createTexture(gl, {
src: tilemapU8,
width: mapWidth,
minMag: gl.NEAREST,
});
function ease(t) {
return Math.cos(t) * .5 + .5;
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function easeLerp(a, b, t) {
return lerp(a, b, ease(t));
}
function render(time) {
time *= 0.001; // convert to seconds;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 1, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
m4.scale(mat, [gl.canvas.width, gl.canvas.height, 1], mat);
const scaleX = easeLerp(.5, 2, time * 1.1);
const scaleY = easeLerp(.5, 2, time * 1.1);
const dispScaleX = 1;
const dispScaleY = 1;
// origin of scale/rotation
const originX = gl.canvas.width * .5;
const originY = gl.canvas.height * .5;
// scroll position in pixels
const scrollX = time % (mapWidth * tileWidth );
const scrollY = time % (mapHeight * tileHeight);
const rotation = time;
const tmat = m4.identity();
m4.translate(tmat, [scrollX, scrollY, 0], tmat);
m4.rotateZ(tmat, rotation, tmat);
m4.scale(tmat, [
gl.canvas.width / tileWidth / scaleX * (dispScaleX),
gl.canvas.height / tileHeight / scaleY * (dispScaleY),
1,
], tmat);
m4.translate(tmat, [
-originX / gl.canvas.width,
-originY / gl.canvas.height,
0,
], tmat);
twgl.setUniforms(programInfo, {
u_matrix: mat,
u_texMatrix: tmat,
u_tilemap: mapTexture,
u_tiles: tileTexture,
u_tilemapSize: [mapWidth, mapHeight],
u_tilesetSize: [tilesAcross, tilesDown],
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
I'm using the following shader sources for vertex and fragment.
Vertex shader source:
#define highp
attribute highp vec3 position;
uniform highp mat4 mvp;
void main(void)
{
gl_Position = mvp * vec4(position, 1.0);
}
Fragment shader source:
#define highp
uniform highp vec3 color;
void main(void)
{
gl_FragColor = vec4(color, 1.0);
}
However, the shader is not working. As shown in the following screen-shot, a 3D object is just colored like a 2D object:
glGetShaderiv with GL_COMPILE_STATUS returns success == TRUE, so there is no shader compile error.
glGetShaderiv(shaderObj, GL_COMPILE_STATUS, &success);
I didn't try glGetError() in the code. I'm going to try it. But I suspect I'm not receiving any OpenGL errors.
I believe I need to adjust the color in vertex and fragment shader. How should I adjust the color in shader sources? Can anyone help me by giving a hint. So far, I couldn't resolve the issue by modifying the sources.
UPDATE
With the help of #Rabbid76 now the 3D object looks good:
I'm using #Rabbid76 code with a small modification: adding the #version 130 to the top of both vertex and fragment shader sources. Looks like my Intel graphics card requires the #version 130 directive, otherwise it throws some warnings and errors:
warning: extension "GL_OES_standard_derivative" unsupported in fragment shader
The #version 130 directive resolves the above warning and its subsequent errors.
Your shader is working correctly. It looks like it's 2D because you don't have any lighting in your shader. An object that is a uniform color will look 2D because there are no depth cues such as self shadowing, or specular highlights, etc.
According to the comment:
I'm studying on lighting, but it looks like to be a huge topic. I wonder if you can guide me to the simplest lighting code which would work in my case
A simple lambertian diffuse "fake" light, directed from the point of view.
The surface normal vector can be calculated approximately by the partial derivative of the view space position in the fragment shader. The partial derivative can be get by the functions dFdx and dFdy. For this is required OpenGL 2.0, OpenGL ES 3.0 or the OES_standard_derivatives extension:
Vertex shader
#define highp
attribute highp vec3 position;
varying vec4 v_clip_pos;
uniform highp mat4 mvp;
void main(void)
{
v_clip_pos = mvp * vec4(position, 1.0);
gl_Position = v_clip_pos;
}
Fragment shader
#extension GL_OES_standard_derivatives : enable
varying vec4 v_clip_pos;
uniform highp vec3 color;
void main()
{
vec3 ndc_pos = v_clip_pos.xyz / v_clip_pos.w;
vec3 dx = dFdx( ndc_pos );
vec3 dy = dFdy( ndc_pos );
vec3 N = normalize(cross(dx, dy));
N *= sign(N.z);
vec3 L = vec3(0.0, 0.0, 1.0);
float NdotL = dot(N, L);
vec3 diffuse_color = color * NdotL;
gl_FragColor = vec4( diffuse_color.rgb, 1.0 );
}
See the WebGL example which demonstrates the shader:
var readInput = true;
function changeEventHandler(event){
readInput = true;
}
(function loadscene() {
var gl, progDraw, vp_size;
var bufCube = {};
function render(delteMS){
Camera.create();
Camera.vp = vp_size;
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// set up draw shader
ShaderProgram.Use( progDraw );
ShaderProgram.SetUniformM44( progDraw, "u_projectionMat44", Camera.Perspective() );
ShaderProgram.SetUniformM44( progDraw, "u_viewMat44", Camera.LookAt() );
var modelMat = IdentityMat44()
modelMat = RotateAxis( modelMat, CalcAng( delteMS, 13.0 ), 0 );
modelMat = RotateAxis( modelMat, CalcAng( delteMS, 17.0 ), 1 );
ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", modelMat );
ShaderProgram.SetUniformF3( progDraw, "color", [0.9, 0.9, 0.5] );
// draw scene
VertexBuffer.Draw( bufCube );
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
}
function initScene() {
canvas = document.getElementById( "canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return null;
var ext_standard_derivatives = gl.getExtension( "OES_standard_derivatives" ); // dFdx, dFdy
if (!ext_standard_derivatives)
alert('no standard derivatives support (no dFdx, dFdy)');
progDraw = ShaderProgram.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
if ( !progDraw.progObj )
return null;
progDraw.inPos = ShaderProgram.AttributeIndex( progDraw, "inPos" );
progDraw.inNV = ShaderProgram.AttributeIndex( progDraw, "inNV" );
// create sphere
var layer_size = 16, circum_size = 32;
var rad_circum = 1.0;
var rad_tube = 0.5;
var sphere_pts = [];
var sphere_nv = [];
sphere_pts.push( 0.0, 0.0, -rad_circum );
sphere_nv.push( 0.0, 0.0, -1.0 );
for ( var i_l = 1; i_l < layer_size; ++ i_l ) {
var angH = (1.0 - i_l / layer_size) * Math.PI;
var h = Math.cos( angH );
var r = Math.sin( angH );
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var circumX = Math.cos(2 * Math.PI * i_c / circum_size);
var circumY = Math.sin(2 * Math.PI * i_c / circum_size);
sphere_pts.push( r * circumX * rad_circum, r * circumY * rad_circum, h * rad_circum );
sphere_nv.push( r * circumX, r * circumY, h );
}
}
sphere_pts.push( 0.0, 0.0, rad_circum );
sphere_nv.push( 0.0, 0.0, 1.0 );
var sphere_inx = [];
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
sphere_inx.push( i_c+1, 0, (i_c+1) % circum_size + 1 )
}
for ( var i_l = 0; i_l < layer_size-2; ++ i_l ) {
var l1 = i_l * circum_size + 1;
var l2 = (i_l+1) * circum_size + 1
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var i_n = (i_c+1) % circum_size;
sphere_inx.push( l1+i_c, l1+i_n, l2+i_c, l1+i_n, l2+i_n, l2+i_c );
}
}
for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
var i_start = 1 + (layer_size-2) * circum_size;
var i_n = (i_c+1) % circum_size;
sphere_inx.push( i_start + i_c, i_start + i_n, sphere_pts.length/3-1 );
}
bufCube = VertexBuffer.Create(
[ { data : sphere_pts, attrSize : 3, attrLoc : progDraw.inPos },
{ data : sphere_nv, attrSize : 3, attrLoc : progDraw.inNV } ],
sphere_inx );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( deltaTime, intervall ) {
return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( deltaTime, intervall, range ) {
var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0
var pos = pos < 1.0 ? pos : (2.0-pos)
return range[0] + (range[1] - range[0]) * pos;
}
function EllipticalPosition( a, b, angRag ) {
var a_b = a * a - b * b
var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );
var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );
return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];
}
glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );
function IdentityMat44() {
var m = new glArrayType(16);
m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 1; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
return m;
};
function RotateAxis(matA, angRad, axis) {
var aMap = [ [1, 2], [2, 0], [0, 1] ];
var a0 = aMap[axis][0], a1 = aMap[axis][1];
var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
var matB = new glArrayType(16);
for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
for ( var i = 0; i < 3; ++ i ) {
matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
}
return matB;
}
function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
function Normalize( v ) {
var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
return [ v[0] / len, v[1] / len, v[2] / len ];
}
var Camera = {};
Camera.create = function() {
this.pos = [0, 1.5, 0.0];
this.target = [0, 0, 0];
this.up = [0, 0, 1];
this.fov_y = 90;
this.vp = [800, 600];
this.near = 0.5;
this.far = 100.0;
}
Camera.Perspective = function() {
var fn = this.far + this.near;
var f_n = this.far - this.near;
var r = this.vp[0] / this.vp[1];
var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
var m = IdentityMat44();
m[0] = t/r; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = t; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = -fn / f_n; m[11] = -1;
m[12] = 0; m[13] = 0; m[14] = -2 * this.far * this.near / f_n; m[15] = 0;
return m;
}
Camera.LookAt = function() {
var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
var mx = Normalize( Cross( this.up, mz ) );
var my = Normalize( Cross( mz, mx ) );
var tx = Dot( mx, this.pos );
var ty = Dot( my, this.pos );
var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos );
var m = IdentityMat44();
m[0] = mx[0]; m[1] = my[0]; m[2] = mz[0]; m[3] = 0;
m[4] = mx[1]; m[5] = my[1]; m[6] = mz[1]; m[7] = 0;
m[8] = mx[2]; m[9] = my[2]; m[10] = mz[2]; m[11] = 0;
m[12] = tx; m[13] = ty; m[14] = tz; m[15] = 1;
return m;
}
var ShaderProgram = {};
ShaderProgram.Create = function( shaderList ) {
var shaderObjs = [];
for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
if ( shderObj == 0 )
return 0;
shaderObjs.push( shderObj );
}
var prog = {}
prog.progObj = this.LinkProgram( shaderObjs )
if ( prog.progObj ) {
prog.attribIndex = {};
var noOfAttributes = gl.getProgramParameter( prog.progObj, gl.ACTIVE_ATTRIBUTES );
for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
var name = gl.getActiveAttrib( prog.progObj, i_n ).name;
prog.attribIndex[name] = gl.getAttribLocation( prog.progObj, name );
}
prog.unifomLocation = {};
var noOfUniforms = gl.getProgramParameter( prog.progObj, gl.ACTIVE_UNIFORMS );
for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
var name = gl.getActiveUniform( prog.progObj, i_n ).name;
prog.unifomLocation[name] = gl.getUniformLocation( prog.progObj, name );
}
}
return prog;
}
ShaderProgram.AttributeIndex = function( prog, name ) { return prog.attribIndex[name]; }
ShaderProgram.UniformLocation = function( prog, name ) { return prog.unifomLocation[name]; }
ShaderProgram.Use = function( prog ) { gl.useProgram( prog.progObj ); }
ShaderProgram.SetUniformI1 = function( prog, name, val ) { if(prog.unifomLocation[name]) gl.uniform1i( prog.unifomLocation[name], val ); }
ShaderProgram.SetUniformF1 = function( prog, name, val ) { if(prog.unifomLocation[name]) gl.uniform1f( prog.unifomLocation[name], val ); }
ShaderProgram.SetUniformF2 = function( prog, name, arr ) { if(prog.unifomLocation[name]) gl.uniform2fv( prog.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF3 = function( prog, name, arr ) { if(prog.unifomLocation[name]) gl.uniform3fv( prog.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF4 = function( prog, name, arr ) { if(prog.unifomLocation[name]) gl.uniform4fv( prog.unifomLocation[name], arr ); }
ShaderProgram.SetUniformM33 = function( prog, name, mat ) { if(prog.unifomLocation[name]) gl.uniformMatrix3fv( prog.unifomLocation[name], false, mat ); }
ShaderProgram.SetUniformM44 = function( prog, name, mat ) { if(prog.unifomLocation[name]) gl.uniformMatrix4fv( prog.unifomLocation[name], false, mat ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
var shaderObj = gl.createShader( shaderStage );
gl.shaderSource( shaderObj, source );
gl.compileShader( shaderObj );
var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : null;
}
ShaderProgram.LinkProgram = function( shaderObjs ) {
var prog = gl.createProgram();
for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
gl.attachShader( prog, shaderObjs[i_sh] );
gl.linkProgram( prog );
status = gl.getProgramParameter( prog, gl.LINK_STATUS );
if ( !status ) alert("Could not initialise shaders");
gl.useProgram( null );
return status ? prog : null;
}
var VertexBuffer = {};
VertexBuffer.Create = function( attributes, indices ) {
var buffer = {};
buffer.buf = [];
buffer.attr = []
for ( var i = 0; i < attributes.length; ++ i ) {
buffer.buf.push( gl.createBuffer() );
buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );
}
buffer.inx = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
buffer.inxLen = indices.length;
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
return buffer;
}
VertexBuffer.Draw = function( bufObj ) {
for ( var i = 0; i < bufObj.buf.length; ++ i ) {
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );
gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( bufObj.attr[i].loc );
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
for ( var i = 0; i < bufObj.buf.length; ++ i )
gl.disableVertexAttribArray( bufObj.attr[i].loc );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
initScene();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision highp float;
attribute vec3 inPos;
attribute vec3 inNV;
varying vec4 v_clip_pos;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
void main()
{
vec4 pos = u_viewMat44 * u_modelMat44 * vec4( inPos, 1.0 );
v_clip_pos = u_projectionMat44 * pos;
gl_Position = v_clip_pos;
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;
varying vec4 v_clip_pos;
uniform highp vec3 color;
void main()
{
vec3 ndc_pos = v_clip_pos.xyz / v_clip_pos.w;
vec3 dx = dFdx( ndc_pos );
vec3 dy = dFdy( ndc_pos );
vec3 N = normalize(cross(dx, dy));
N *= sign(N.z);
vec3 L = vec3(0.0, 0.0, 1.0);
float NdotL = dot(N, L);
vec3 diffuse_color = color * NdotL;
gl_FragColor = vec4( diffuse_color.rgb, 1.0 );
}
</script>
<canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>
I found nice water simulation from codepen and modified it with help from other thread here (can't find it anymore though).
I have used three.js couple of times before, but now I just can't comprehend why camera positioning/rotation/aspect/etc isn't working. No matter what coordinates or angle I give to camera and use updateProjectionMatrix nothing happens, camera just stays in one place.
I commented out resize events etc, since they don't do anything also.
Entire code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<style type="text/css">
body {
overflow: hidden;
margin: 0;
height: 100%;
}
</style>
<title></title>
<script type='text/javascript'>//<![CDATA[
window.onload=function(){
// init camera, scene, renderer
var scene, camera, renderer;
scene = new THREE.Scene();
var fov = 75,
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(fov, aspect, 0.1, 1000);
camera.position.z = 200;
camera.rotate.z = 1.5707963268;
camera.updateProjectionMatrix();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xc4c4c4);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var clock = new THREE.Clock();
var tuniform = {
time: {
type: 'f',
value: 0.1
},
resolution: {
type: 'v2',
value: new THREE.Vector2()
},
mouse: {
type: 'v4',
value: new THREE.Vector2()
}
};
// Mouse position in - 1 to 1
renderer.domElement.addEventListener('mousedown', function(e) {
//var canvas = renderer.domElement;
//var rect = canvas.getBoundingClientRect();
//tuniform.mouse.value.x = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
//tuniform.mouse.value.y = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
renderer.domElement.addEventListener('mouseup', function(e) {
//var canvas = renderer.domElement;
//var rect = canvas.getBoundingClientRect();
//tuniform.mouse.value.z = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
//tuniform.mouse.value.w = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
// resize canvas function
window.addEventListener('resize',function() {
//camera.aspect = window.innerWidth / window.innerHeight;
//camera.updateProjectionMatrix();
//renderer.setSize(window.innerWidth, window.innerHeight);
});
tuniform.resolution.value.x = window.innerWidth;
tuniform.resolution.value.y = window.innerHeight;
// Create Plane
var material = new THREE.ShaderMaterial({
uniforms: tuniform,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var mesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(window.innerWidth, window.innerHeight, 40), material
);
scene.add(mesh);
// draw animation
function render(time) {
tuniform.time.value += clock.getDelta();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
}//]]>
</script>
</head>
<body>
<!-- THIS is OPENGL Shading language scripts -->
<script id="vertex-shader" type="no-js">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragment-shader" type="no-js">
#ifdef GL_ES
precision mediump float;
#endif
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;
const int NUM_STEPS = 8;
const float PI = 3.1415;
const float EPSILON = 1e-3;
float EPSILON_NRM = 0.1 / resolution.x;
// sea
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 2.0;
const float SEA_SPEED = 0.5;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.1,0.19,0.22); //meren pohjaväri
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
const float SKY_INTENSITY = 1.0;
#define SEA_TIME time * SEA_SPEED
// math
mat4 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x),cos(ang.x));
vec2 a2 = vec2(sin(ang.y),cos(ang.y));
vec2 a3 = vec2(sin(ang.z),cos(ang.z));
mat4 m;
m[0] = vec4(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x,0.0);
m[1] = vec4(-a2.y*a1.x,a1.y*a2.y,a2.x,0.0);
m[2] = vec4(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y,0.0);
m[3] = vec4(0.0,0.0,0.0,1.0);
return m;
}
vec3 rotate(vec3 v, mat4 m) {
return vec3(dot(v,m[0].xyz),dot(v,m[1].xyz),dot(v,m[2].xyz));
}
float hash( vec2 p ) {
float h = dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
vec2 i = floor( p );
vec2 f = fract( p );
vec2 u = f*f*(3.0-2.0*f);
return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
hash( i + vec2(1.0,0.0) ), u.x),
mix( hash( i + vec2(0.0,1.0) ),
hash( i + vec2(1.0,1.0) ), u.x), u.y);
}
// lighting
float diffuse(vec3 n,vec3 l,float p) { return pow(dot(n,l) * 0.4 + 0.6,p); }
float specular(vec3 n,vec3 l,vec3 e,float s) {
float nrm = (s + 8.0) / (3.1415 * 8.0);
return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}
// sky
vec3 sky_color(vec3 e) {
e.y = max(e.y,0.0);
vec3 ret;
ret.x = pow(1.0-e.y,2.0);
ret.y = 1.0-e.y;
ret.z = 0.6+(1.0-e.y)*0.4;
return ret * SKY_INTENSITY;
}
// sea
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0-abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv,swv,wv);
return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
}
float map(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
mat2 m = mat2(1.6,1.2,-1.2,1.6);
float d, h = 0.0;
for(int i = 0; i < ITER_GEOMETRY; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
float map_detailed(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
mat2 m = mat2(1.6,1.2,-1.2,1.6);
float d, h = 0.0;
for(int i = 0; i < ITER_FRAGMENT; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
vec3 sea_color(in vec3 p, in vec3 n, in vec3 eye, in vec3 dist) {
float fresnel_o = 1.0 - max(dot(n,-eye),0.0);
float fresnel = pow(fresnel_o,3.0) * 0.65;
// reflection
vec3 refl = sky_color(reflect(eye,n));
// color
vec3 ret = SEA_BASE;
ret = mix(ret,refl,fresnel);
// wave peaks
float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
ret += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
return ret;
}
// tracing
vec3 getNormal(vec3 p, float eps) {
vec3 n;
n.y = map_detailed(p);
n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
n.y = eps;
return normalize(n);
}
float hftracing(vec3 ori, vec3 dir, out vec3 p) {
float tm = 0.0;
float tx = 1000.0;
float hx = map(ori + dir * tx);
if(hx > 0.0) return tx;
float hm = map(ori + dir * tm);
float tmid = 0.0;
for(int i = 0; i < NUM_STEPS; i++) {
tmid = mix(tm,tx, hm/(hm-hx));
p = ori + dir * tmid;
float hmid = map(p);
if(hmid < 0.0) {
tx = tmid;
hx = hmid;
} else {
tm = tmid;
hm = hmid;
}
}
return tmid;
}
// main
void main(void) {
vec2 uv = gl_FragCoord.xy / resolution.xy;
uv = 1.0 - uv * 2.0;
uv.x *= resolution.x / resolution.y;
//uv = (surfacePosition+vec2(0., .5))*17. + 5E-3*(pow(length(surfacePosition+vec2(0. ,0.5)), -2.));
uv.y *= -1.;
//uv.y += -2.;
// ray
vec3 ang = vec3(0.0,0.003, pow(time, 0.6));
ang = vec3(0.0,clamp(2.0-mouse.y*0.01,-0.3,PI),mouse.x*0.01);
vec3 ori = vec3(0.0,3.5,time*.05);
vec3 dir = normalize(vec3(uv.xy,-2.0));
dir.z -= length(uv) * 0.15;
//dir = rotate(normalize(dir),ang);
// tracing
vec3 p;
float dens = hftracing(ori,dir,p);
vec3 dist = p - ori;
vec3 n = getNormal(p, dot(dist,dist)*EPSILON_NRM);
// color
vec3 color = sea_color(p,n,dir,dist);
vec3 light = normalize(vec3(0.0,1.0,0.8));
color += vec3(diffuse(n,light,80.0) * SEA_WATER_COLOR) * 0.12;
color += vec3(specular(n,light,dir,60.0));
// post
color = mix(sky_color(dir),color,pow(smoothstep(0.0,-0.05,dir.y),0.3));
color = pow(color,vec3(0.75));
gl_FragColor = vec4(color,1.0);
}
</script>
<script>
// tell the embed parent frame the height of the content
if (window.parent && window.parent.parent){
window.parent.parent.postMessage(["resultsFrame", {
height: document.body.getBoundingClientRect().height,
slug: "uz6yo2w3"
}], "*")
}
</script>
</body>
</html>
There are so much try in this code that it's not even clear to me what you're trying to do, but I can give some hints :
Change the camera.rotate to camera.rotation l. 30
Your mouse events are commented. If you want to rotate the camera with the mouse, you're gonna have to add a mousemove event ;
By the way, l. 50 you send a vec4 but loads it as a vec2 l. 126 ;
When the window is resized, you may also want to update the new resolution to the shader ;
l. 304, change the Z component of the camera's origin from vec3 ori = vec3(0.0, 3.5, time * 5.0); to vec3 ori = vec3(0.0, 3.5, time * 5.0); so you can see the camera moving along the sea ;
l. 306, instead of dir = rotate(normalize(dir), ang); add the initial dir = normalize(dir) * fromEuler(ang); (ang is the angle of the camera) ;
l. 149, change your mat4 fromEuler(vec3 ang){...} to the initial mat3 fromEuler(vec3 ang){...} function ;
l. 301, just put vec3 ang = vec3(0.0, 0.0, 0.0); and play with it. You may use mouse coordinates in this function, depending on how you want the user interact with the camera.
I'd like to normalize monochrome image pixels in that way the minimum value is black, the maximum is white and values in between are spread proportionally.
Currently I do it in canvas in two steps, but I believe it should be faster in WebGL.
I can imagine manipulating colors via fragment shader, but I couldn't find any efficient way for (1) determining the actual range of the image, nor (2) approach for passing this info to another fragment shader, which could then perform that grey level normalization.
Seems like you could generate progressively smaller textures in your fragment shader and in each texture write out min and max. So for example if you have a 16x16 texture then for every 2x2 pixels write out 1 pixels that represent the max.
vec4 c00 = texture2D(sampler, uv);
vec4 c10 = texture2D(sampler, uv + vec2(onePixelRight, 0));
vec4 c01 = texture2D(sampler, uv + vec2(0, onePixelUp));
vec4 c11 = texture2D(sampler, uv + vec2(onePixelRight, onePixelUp);
gl_FragColor = max(max(c00, c10), max(c01, c11));
Repeat until you get to 1x1 pixel. Do the same for min. When you're done you'll have 2 1x1 pixel textures. Either read them with readPixels or pass them to another shader as your range.
It might be faster to use larger chunks, instead of 2x2 do 8x8 or 16x16 areas but keep reducing until you get to 1x1 pixels
In pseudo code.
// setup
textures = [];
framebuffers = [];
cellSize = 16
maxDimension = max(width, height)
w = width
h = height
while w > 1 || h > 1
w = max(1, w / cellSize)
h = max(1, h / cellSize)
textures.push(create Texture of size w, h)
framebuffers.push(create framebuffer and attach texture)
}
// computation
bind original image as input texture
foreach(framebuffer)
bind framebuffer
render to framebuffer with max GLSL shader above
bind texture of current framebuffer as input to next iteration
}
Now the last framebuffer as a 1x1 pixel texture with the max value in it.
"use strict";
var cellSize = 2;
// make a texture as our source
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = "rgb(12, 34, 56)";
ctx.fillRect(20, 30, 1, 1);
ctx.fillStyle = "rgb(254, 243, 232)";
ctx.fillRect(270, 140, 1, 1);
var canvas = document.createElement("canvas");
var m4 = twgl.m4;
var gl = canvas.getContext("webgl");
var fsSrc = document.getElementById("max-fs").text.replace("$(cellSize)s", cellSize);
var programInfo = twgl.createProgramInfo(gl, ["vs", fsSrc]);
var unitQuadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
var framebufferInfo = twgl.createFramebufferInfo(gl);
var srcTex = twgl.createTexture(gl, {
src: ctx.canvas,
min: gl.NEAREST,
mag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
});
var framebuffers = [];
var w = ctx.canvas.width;
var h = ctx.canvas.height;
while (w > 1 || h > 1) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
// creates a framebuffer and creates and attaches an RGBA/UNSIGNED texture
var fb = twgl.createFramebufferInfo(gl, [
{ min: gl.NEAREST, max: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE },
], w, h);
framebuffers.push(fb);
}
var uniforms = {
u_srcResolution: [ctx.canvas.width, ctx.canvas.height],
u_texture: srcTex,
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, unitQuadBufferInfo);
var w = ctx.canvas.width;
var h = ctx.canvas.height;
framebuffers.forEach(function(fbi, ndx) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
uniforms.u_dstResolution = [w, h];
twgl.bindFramebufferInfo(gl, fbi);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, unitQuadBufferInfo);
uniforms.u_texture = fbi.attachments[0];
uniforms.u_srcResolution = [w, h];
});
var p = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, p);
log("max: ", p[0], p[1], p[2]);
function log() {
var elem = document.createElement("pre");
elem.appendChild(document.createTextNode(Array.prototype.join.call(arguments, " ")));
document.body.appendChild(elem);
}
<script id="vs" type="not-js">
attribute vec4 position;
void main() {
gl_Position = position;
}
</script>
<script id="max-fs" type="not-js">
precision mediump float;
#define CELL_SIZE $(cellSize)s
uniform sampler2D u_texture;
uniform vec2 u_srcResolution;
uniform vec2 u_dstResolution;
void main() {
// compute the first pixel the source cell
vec2 srcPixel = floor(gl_FragCoord.xy) * float(CELL_SIZE);
// one pixel in source
vec2 onePixel = vec2(1) / u_srcResolution;
// uv for first pixel in cell. +0.5 for center of pixel
vec2 uv = (srcPixel + 0.5) * onePixel;
vec4 maxColor = vec4(0);
for (int y = 0; y < CELL_SIZE; ++y) {
for (int x = 0; x < CELL_SIZE; ++x) {
maxColor = max(maxColor, texture2D(u_texture, uv + vec2(x, y) * onePixel));
}
}
gl_FragColor = maxColor;
}
</script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Also if you have WEBGL_draw_buffers support you do both min and max at the same time writing to 2 different framebuffer attachments
"use strict";
var cellSize = 2;
// make a texture as our source
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = "rgb(128, 128, 128)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "rgb(12, 34, 56)";
ctx.fillRect(20, 30, 1, 1);
ctx.fillStyle = "rgb(254, 243, 232)";
ctx.fillRect(270, 140, 1, 1);
var canvas = document.createElement("canvas");
var m4 = twgl.m4;
var gl = canvas.getContext("webgl");
var ext = gl.getExtension("WEBGL_draw_buffers");
if (!ext) {
alert("sample requires WEBGL_draw_buffers");
}
var fsSrc = document.querySelector("#minmax-fs").text.replace("$(cellSize)s", cellSize);
var programInfo = twgl.createProgramInfo(gl, ["vs", fsSrc]);
var unitQuadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
var srcTex = twgl.createTexture(gl, {
src: ctx.canvas,
min: gl.NEAREST,
mag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
});
var framebuffers = [];
var w = ctx.canvas.width;
var h = ctx.canvas.height;
while (w > 1 || h > 1) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
// creates a framebuffer and creates and attaches 2 RGBA/UNSIGNED textures
var fbi = twgl.createFramebufferInfo(gl, [
{ min: gl.NEAREST, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
{ min: gl.NEAREST, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
], w, h);
ext.drawBuffersWEBGL([ext.COLOR_ATTACHMENT0_WEBGL, ext.COLOR_ATTACHMENT1_WEBGL]);
framebuffers.push(fbi);
}
// need separate FBs to read the output
var lastFBI = framebuffers[framebuffers.length - 1];
var minFBI = twgl.createFramebufferInfo(gl, [
{ attachment: lastFBI.attachments[0] }
], 1, 1);
var maxFBI = twgl.createFramebufferInfo(gl, [
{ attachment: lastFBI.attachments[1] }
], 1, 1);
var uniforms = {
u_srcResolution: [ctx.canvas.width, ctx.canvas.height],
u_minTexture: srcTex,
u_maxTexture: srcTex,
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, unitQuadBufferInfo);
var w = ctx.canvas.width;
var h = ctx.canvas.height;
framebuffers.forEach(function(fbi, ndx) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
uniforms.u_dstResolution = [w, h];
twgl.bindFramebufferInfo(gl, fbi);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, unitQuadBufferInfo);
uniforms.u_minTexture = fbi.attachments[0];
uniforms.u_maxTexture = fbi.attachments[1];
uniforms.u_srcResolution = [w, h];
});
var p = new Uint8Array(4);
twgl.bindFramebufferInfo(gl, minFBI);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, p);
log("min: ", p[0], p[1], p[2]);
twgl.bindFramebufferInfo(gl, maxFBI);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, p);
log("max: ", p[0], p[1], p[2]);
function log() {
var elem = document.createElement("pre");
elem.appendChild(document.createTextNode(Array.prototype.join.call(arguments, " ")));
document.body.appendChild(elem);
}
<script id="vs" type="not-js">
attribute vec4 position;
void main() {
gl_Position = position;
}
</script>
<script id="minmax-fs" type="not-js">
#extension GL_EXT_draw_buffers : require
precision mediump float;
#define CELL_SIZE $(cellSize)s
uniform sampler2D u_minTexture;
uniform sampler2D u_maxTexture;
uniform vec2 u_srcResolution;
uniform vec2 u_dstResolution;
void main() {
// compute the first pixel the source cell
vec2 srcPixel = floor(gl_FragCoord.xy) * float(CELL_SIZE);
// one pixel in source
vec2 onePixel = vec2(1) / u_srcResolution;
// uv for first pixel in cell. +0.5 for center of pixel
vec2 uv = (srcPixel + 0.5) / u_srcResolution;
vec4 minColor = vec4(1);
vec4 maxColor = vec4(0);
for (int y = 0; y < CELL_SIZE; ++y) {
for (int x = 0; x < CELL_SIZE; ++x) {
vec2 off = uv + vec2(x, y) * onePixel;
minColor = min(minColor, texture2D(u_minTexture, off));
maxColor = max(maxColor, texture2D(u_maxTexture, off));
}
}
gl_FragData[0] = minColor;
gl_FragData[1] = maxColor;
}
</script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Now that you have the answer you can pass it to another shader to "contrastify" your texture
If you read out the values then
uniform vec4 u_minColor;
uniform vec4 u_maxColor;
uniform sampler2D u_texture;
...
vec4 color = texture2D(u_texture, uv);
vec4 range = u_maxColor - u_minColor;
gl_FragColor = (color - u_minColor) * range;
If you just want to pass in the textures without reading them out then
uniform sampler2D u_minColor;
uniform sampler2D u_maxColor;
uniform sampler2D u_texture;
...
vec4 minColor = texture2D(u_minColor, vec2(0));
vec4 maxColor = texture2D(u_maxColor, vec2(0));
vec4 color = texture2D(u_texture, uv);
vec4 range = maxColor - minColor;
gl_FragColor = vec4(((color - minColor) / range).rgb, 1);
I don't know if one is better than the other. I'd assume reading from a texture is slower than reading from a uniform but for a shader this small the performance difference might be minimal
"use strict";
var cellSize = 16;
var canvas = document.createElement("canvas");
var m4 = twgl.m4;
var gl = canvas.getContext("webgl");
var ext = gl.getExtension("WEBGL_draw_buffers");
if (!ext) {
alert("sample requires WEBGL_draw_buffers");
}
var fsSrc = document.querySelector("#minmax-fs").text.replace("$(cellSize)s", cellSize);
var programInfo = twgl.createProgramInfo(gl, ["vs", fsSrc]);
var contrastProgramInfo = twgl.createProgramInfo(gl, ["vs", "contrastify-fs"]);
var unitQuadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
var srcTex = twgl.createTexture(gl, {
src: "http://i.imgur.com/rItAVSG.jpg",
crossOrigin: "",
min: gl.NEAREST,
mag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
}, function(err, srcTex, img) {
img.style.width = "300px";
img.style.height = "150px";
log("before");
document.body.appendChild(img);
log("after");
document.body.appendChild(canvas);
var framebuffers = [];
var w = img.width;
var h = img.height;
while (w > 1 || h > 1) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
// creates a framebuffer and creates and attaches 2 RGBA/UNSIGNED textures
var fbi = twgl.createFramebufferInfo(gl, [
{ min: gl.NEAREST, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
{ min: gl.NEAREST, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
], w, h);
ext.drawBuffersWEBGL([ext.COLOR_ATTACHMENT0_WEBGL, ext.COLOR_ATTACHMENT1_WEBGL]);
framebuffers.push(fbi);
}
// need separate FBs to read the output
var lastFBI = framebuffers[framebuffers.length - 1];
var minFBI = twgl.createFramebufferInfo(gl, [
{ attachment: lastFBI.attachments[0] }
], 1, 1);
var maxFBI = twgl.createFramebufferInfo(gl, [
{ attachment: lastFBI.attachments[1] }
], 1, 1);
var uniforms = {
u_srcResolution: [img.width, img.height],
u_minTexture: srcTex,
u_maxTexture: srcTex,
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, unitQuadBufferInfo);
var w = img.width;
var h = img.height;
framebuffers.forEach(function(fbi, ndx) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
uniforms.u_dstResolution = [w, h];
twgl.bindFramebufferInfo(gl, fbi);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, unitQuadBufferInfo);
uniforms.u_minTexture = fbi.attachments[0];
uniforms.u_maxTexture = fbi.attachments[1];
uniforms.u_srcResolution = [w, h];
});
twgl.bindFramebufferInfo(gl, null);
gl.useProgram(contrastProgramInfo.program);
twgl.setUniforms(contrastProgramInfo, {
u_resolution: [img.width, img.height],
u_texture: srcTex,
u_minColor: fbi.attachments[0],
u_maxColor: fbi.attachments[1],
});
twgl.drawBufferInfo(gl, unitQuadBufferInfo);
});
function log() {
var elem = document.createElement("pre");
elem.appendChild(document.createTextNode(Array.prototype.join.call(arguments, " ")));
document.body.appendChild(elem);
}
img, canvas { margin: 5px; border: 1px solid black; }
<script id="vs" type="not-js">
attribute vec4 position;
void main() {
gl_Position = position;
}
</script>
<script id="minmax-fs" type="not-js">
#extension GL_EXT_draw_buffers : require
precision mediump float;
#define CELL_SIZE $(cellSize)s
uniform sampler2D u_minTexture;
uniform sampler2D u_maxTexture;
uniform vec2 u_srcResolution;
uniform vec2 u_dstResolution;
void main() {
// compute the first pixel the source cell
vec2 srcPixel = floor(gl_FragCoord.xy) * float(CELL_SIZE);
// one pixel in source
vec2 onePixel = vec2(1) / u_srcResolution;
// uv for first pixel in cell. +0.5 for center of pixel
vec2 uv = (srcPixel + 0.5) / u_srcResolution;
vec4 minColor = vec4(1);
vec4 maxColor = vec4(0);
for (int y = 0; y < CELL_SIZE; ++y) {
for (int x = 0; x < CELL_SIZE; ++x) {
vec2 off = uv + vec2(x, y) * onePixel;
minColor = min(minColor, texture2D(u_minTexture, off));
maxColor = max(maxColor, texture2D(u_maxTexture, off));
}
}
gl_FragData[0] = minColor;
gl_FragData[1] = maxColor;
}
</script>
<script id="contrastify-fs" type="not-fs">
precision mediump float;
uniform sampler2D u_minColor;
uniform sampler2D u_maxColor;
uniform sampler2D u_texture;
uniform vec2 u_resolution;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
uv.y = 1.0 - uv.y;
vec4 minColor = texture2D(u_minColor, vec2(0));
vec4 maxColor = texture2D(u_maxColor, vec2(0));
vec4 color = texture2D(u_texture, uv);
vec4 range = maxColor - minColor;
gl_FragColor = vec4(((color - minColor) / range).rgb, 1);
}
</script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
As for monochrome just change the src textures to gl.LUMINANCE
I'm trying to write something with WebGL and Dart but I'm having problems. This is my source code.
This is my main file:
library warmup;
import 'dart:html';
import 'dart:math';
import 'dart:web_gl' as WebGL;
import 'dart:typed_data';
import 'package:vector_math/vector_math.dart';
part 'graphics.dart';
WebGL.RenderingContext gl;
CanvasElement canvas;
QuadRenderer renderer;
Random random = new Random();
void main() {
querySelector("#paragraf").setInnerHtml("HELLO!");
canvas = querySelector("#game_canvas");
new Game().start();
}
class Game {
Texture test = new Texture("tex/test.png");
void start() {
gl = canvas.getContext3d();
if (gl == null) {
print("No WebGL!");
}
Texture.loadAll();
renderer = new QuadRenderer();
renderer.projMatrix = makeOrthographicMatrix(0, canvas.width, 0, canvas.height, -1, 1);
gl.disable(WebGL.DEPTH_TEST);
gl.enable(WebGL.BLEND);
gl.blendFunc(WebGL.SRC_ALPHA, WebGL.ONE_MINUS_SRC_ALPHA);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
window.requestAnimationFrame(_update);
}
int now, last = new DateTime.now().millisecondsSinceEpoch;
double unprocessedFrames;
void _update(double time) {
now = new DateTime.now().millisecondsSinceEpoch;
unprocessedFrames = (now - last) * 60 / 1000;
while (unprocessedFrames > 1.0) {
tick();
unprocessedFrames--;
}
renderGame();
window.requestAnimationFrame(_update);
}
void tick() {
}
void renderGame() {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(WebGL.COLOR_BUFFER_BIT);
renderer.bindTexture(test);
renderer.renderQuad(new Vector2(canvas.width / 2.0, canvas.height / 2.0), 128, 128, 0, 0, false);
}
}
This is my graphics.dart file:
part of warmup;
class Texture {
static List<Texture> _pending = new List<Texture>();
String url;
int width, height;
WebGL.Texture data;
bool loaded = false;
Texture(this.url) {
if (gl == null) {
_pending.add(this);
}
else {
load();
}
}
static void loadAll() {
_pending.forEach((e) => e.load());
_pending.clear();
}
void load() {
ImageElement img = new ImageElement();
data = gl.createTexture();
img.onLoad.listen((e) {
gl.bindTexture(WebGL.TEXTURE_2D, data);
gl.texImage2DImage(WebGL.TEXTURE_2D, 0, WebGL.RGBA, WebGL.RGBA, WebGL.UNSIGNED_BYTE, img);
gl.texParameteri(WebGL.TEXTURE_2D, WebGL.TEXTURE_MIN_FILTER, WebGL.NEAREST);
gl.texParameteri(WebGL.TEXTURE_2D, WebGL.TEXTURE_MAG_FILTER, WebGL.NEAREST);
width = img.width;
height = img.height;
loaded = true;
});
img.src = url;
}
}
class Vertex {
Vector2 pos;
Vector4 color;
Vector2 texCoord;
static int elementBytes = 8;
static int posElementCount = 2;
static int colorElementCount = 4;
static int textureElementCount = 2;
static int posBytesCount = posElementCount * elementBytes;
static int colorByteCount = colorElementCount * elementBytes;
static int textureByteCount = textureElementCount * elementBytes;
static int posByteOffset = 0;
static int colorByteOffset = posByteOffset + posBytesCount;
static int textureByteOffset = colorByteOffset + colorByteCount;
static int elementCount = posElementCount +
colorElementCount + textureElementCount;
static int stride = posBytesCount + colorByteCount +
textureByteCount;
Vertex() {
pos = new Vector2(0.0, 0.0);
color = new Vector4(1.0, 1.0, 1.0, 1.0);
texCoord = new Vector2(0.0, 0.0);
}
List<double> getElements() {
List<double> result;
result = [pos.x, pos.y, color.r, color.g, color.b, color.a, texCoord.x, texCoord.y];
return result;
}
}
class QuadRenderer {
String _vsSource = """
precision highp float;
attribute vec2 a_pos;
attribute vec2 a_texCoord;
attribute vec4 a_color;
uniform mat4 proj;
uniform mat4 model;
varying vec2 v_texCoord;
varying vec4 v_pos;
varying vec4 v_color;
void main() {
v_pos = proj * model * vec4(a_pos, 0, 1);
v_color = a_color;
gl_Position = proj * model * vec4(a_pos, 0, 1);
}
""", _fsSource = """
precision highp float;
uniform sampler2D texture;
varying vec2 v_texCoord;
varying vec4 v_pos;
varying vec4 v_color;
void main() {
vec4 texColor = texture2D(texture, v_texCoord);
gl_FragColor = vec4(1, 1, 1, 1);
}
""";
WebGL.Shader vs, fs;
WebGL.Program program;
WebGL.Buffer vab, iab;
int posLocation;
Matrix4 projMatrix;
Vertex v0, v1, v2, v3;
WebGL.UniformLocation projLocation, modelLocation;
Texture texture;
void renderQuad(Vector2 pos, num w, num h, num uo, num vo, bool normalize, {Vector4 color, double rotation}) {
if (!texture.loaded) return;
if (color == null) color = new Vector4(1.0, 1.0, 1.0, 1.0);
if (rotation == null) rotation = 0.0;
Matrix4 model = new Matrix4.identity();
model.translate(pos.x, pos.y);
model.scale(w*1.0, h*1.0, 0.0);
gl.uniformMatrix4fv(modelLocation, false, model.storage);
gl.uniformMatrix4fv(projLocation, false, projMatrix.storage);
if (normalize) {
uo /= texture.width;
vo /= texture.height;
}
v0.color = v1.color = v2.color = v3.color = color;
v0.texCoord = new Vector2(uo + w * 1.0, vo + h * 1.0); v1.texCoord = new Vector2(uo + w * 1.0, vo * 1.0);
v2.texCoord = new Vector2(uo * 1.0, vo * 1.0); v3.texCoord = new Vector2(uo * 1.0, vo + h * 1.0);
_compileVertices();
gl.drawElements(WebGL.TRIANGLES, 6, WebGL.UNSIGNED_SHORT, 0);
}
void bindTexture(Texture tex) {
texture = tex;
gl.bindTexture(WebGL.TEXTURE_2D, texture.data);
}
void _compileVertices() {
List<Vertex> vertices = [v0, v1, v2, v3];
Float32List vertexBuffer = new Float32List(vertices.length * Vertex.elementCount);
for (int i = 0; i < vertices.length; i++) {
Vertex vertex = vertices[i];
vertexBuffer.setAll(i * Vertex.elementCount, vertex.getElements());
}
gl.bindBuffer(WebGL.ARRAY_BUFFER, vab);
gl.bufferData(WebGL.ARRAY_BUFFER, vertexBuffer, WebGL.STATIC_DRAW);
gl.vertexAttribPointer(0, Vertex.posElementCount, WebGL.FLOAT, false, Vertex.stride, Vertex.posByteOffset);
gl.vertexAttribPointer(1, Vertex.colorElementCount, WebGL.FLOAT, false, Vertex.stride, Vertex.colorByteOffset);
gl.vertexAttribPointer(2, Vertex.textureElementCount, WebGL.FLOAT, false, Vertex.stride, Vertex.textureByteOffset);
}
QuadRenderer() {
vs = gl.createShader(WebGL.VERTEX_SHADER);
gl.shaderSource(vs, _vsSource);
gl.compileShader(vs);
if (!gl.getShaderParameter(vs, WebGL.COMPILE_STATUS)) {
throw gl.getShaderInfoLog(vs);
}
fs = gl.createShader(WebGL.FRAGMENT_SHADER);
gl.shaderSource(fs, _fsSource);
gl.compileShader(fs);
if (!gl.getShaderParameter(fs, WebGL.COMPILE_STATUS)) {
throw gl.getShaderInfoLog(fs);
}
program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, WebGL.LINK_STATUS)) {
throw gl.getProgramInfoLog(program);
}
projLocation = gl.getUniformLocation(program, "proj");
modelLocation = gl.getUniformLocation(program, "model");
gl.useProgram(program);
v0 = new Vertex(); v1 = new Vertex(); v2 = new Vertex(); v3 = new Vertex();
v0.pos = new Vector2(0.5, 0.5); v1.pos = new Vector2(0.5, -0.5);
v2.pos = new Vector2(-0.5, -0.5); v3.pos = new Vector2(-0.5, 0.5);
gl.bindAttribLocation(program, 0, "a_pos");
gl.bindAttribLocation(program, 1, "a_color");
gl.bindAttribLocation(program, 2, "a_texCoord");
List<Vertex> vertices = [v0, v1, v2, v3];
Float32List vertexBuffer = new Float32List(vertices.length * Vertex.elementCount);
for (int i = 0; i < vertices.length; i++) {
Vertex vertex = vertices[i];
vertexBuffer.setAll(i * Vertex.elementCount, vertex.getElements());
}
vab = gl.createBuffer();
gl.bindBuffer(WebGL.ARRAY_BUFFER, vab);
gl.bufferData(WebGL.ARRAY_BUFFER, vertexBuffer, WebGL.STATIC_DRAW);
gl.vertexAttribPointer(0, Vertex.posElementCount, WebGL.FLOAT, false, Vertex.stride, Vertex.posByteOffset);
gl.vertexAttribPointer(1, Vertex.colorElementCount, WebGL.FLOAT, false, Vertex.stride, Vertex.colorByteOffset);
gl.vertexAttribPointer(2, Vertex.textureElementCount, WebGL.FLOAT, false, Vertex.stride, Vertex.textureByteOffset);
Int16List indexBuffer = new Int16List(6);
indexBuffer.setAll(0, [0, 1, 2, 0, 2, 3]);
iab = gl.createBuffer();
gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, iab);
gl.bufferData(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer, WebGL.STATIC_DRAW);
}
}
The problem was that the render call renderGame() doesn't work. I know that WebGL work, cause the clear color works fine. I do not know what the problem is, so any suggestion is appreciated.
How about using a rendering library or game engine like Pixi Dart or StageXL instead of plain WebGL?
Those two libraries also abstract the renderer backend (use WebGL whenever possible and use canvas as a fallback).
Hope that helps if you're thinking on developing a game :)