How to position an object for drawing in webgl? and why - javascript

I've managed to make a webgl example all in one file with no included libraries, and only functions that are being used: https://jsfiddle.net/vmLab6jr/
I'm drawing a square made of 2 triangles and I'm making it move farther away and closer to the camera. I want to understand how this part works:
// Now move the drawing position a bit to where we want to start
// drawing the square.
mvMatrix = [
[1,0,0,0],
[0,1,0,0],
[0,0,1,-12+Math.sin(g.loops/6)*4],
[0,0,0,1]
];
var mvUniform = gl.getUniformLocation(g.shaderProgram, "uMVMatrix");
gl.uniformMatrix4fv(mvUniform, false, g.float32(mvMatrix));
Why does webgl want a 4x4 matrix to set the position for drawing an object? Or is there a way to use 1x3, like [x,y,z]? Is it because the shaders I'm using we're arbitrarily set to 4x4?
I cannot find information on what uniformMatrix4fv() does and when and why it's used and what the alternatives are.
Why does the element [2][3] control the z of the object?
I know it has something to do with the frustum matrix being 4x4. And that same spot in the frustum matrix has D, where var D = -2*zfar*znear/(zfar-znear); But to change the x of the object I'm drawing I need to change [0][3] but that slot in the frustum matrix just has a 0.
function makeFrustum(left, right, bottom, top, znear, zfar)
{
var X = 2*znear/(right-left);
var Y = 2*znear/(top-bottom);
var A = (right+left)/(right-left);
var B = (top+bottom)/(top-bottom);
var C = -(zfar+znear)/(zfar-znear);
var D = -2*zfar*znear/(zfar-znear);
return [
[X, 0, A, 0],
[0, Y, B, 0],
[0, 0, C, D],
[0, 0, -1, 0]
];
}
I've been using this tutorial: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL

WebGL does not want a 4x4 matrix. WebGL is just a rasterization library
All it cares about is you provide a vertex shader that fills in a special variable called gl_Position with a clip space coordinate and then you also provide a fragment shader that sets the special variable gl_FragColor with a color.
No matrices are required to do that. Any matrices you use are yours, provided by you to code you supply. There are no required matrices in WebGL.
That said if you follow these tutorials they will eventually lead you to how to use matrices and how the frustum function works
There's also this Q&A: Trying to understand the math behind the perspective matrix in WebGL
As for your multiple questions
Why does webgl want a 4x4 matrix to set the position for drawing an object?
It doesn't. The shader you provided does.
Or is there a way to use 1x3, like [x,y,z]?
Yes, provide a shader that uses 1x3 math
Is it because the shaders I'm using we're arbitrarily set to 4x4?
Yes
I cannot find information on what uniformMatrix4fv() does and when and why it's used and what the alternatives are.
WebGL 1.0 is based on OpenGL ES 2.0 and so the WebGL spec basically says "look at the OpenGL ES 2.0 spec". Specifically it says
1.1 Conventions
...
The remaining sections of this document are intended to be read in conjunction with the OpenGL ES 2.0 specification (2.0.25 at the time of this writing, available from the Khronos OpenGL ES API Registry). Unless otherwise specified, the behavior of each method is defined by the OpenGL ES 2.0 specification.
As for uniformMatrix4fv the various uniform functions are used to set global variables you declared inside the shaders you provided. These global variables are called uniforms because they keep a uniform value from iteration to iteration of your shaders. That's in contrast to 2 other kinds of shader inputs. One called attributes which generally pull the next set of values out of buffers during each iteration of your vertex shader. The other type are called varyings which you set in your vertex shader and are interpolated for each iteration of your fragment shader.

Related

3D model in HTML/CSS; Calculate Euler rotation of triangle

TLDR; Given a set of triangle vertices and a normal vector (all in unit space), how do I calculate X, Y, Z Euler rotation angles of the triangle in world space?
I am attemping to display a 3D model in HTML - with actual HTML tags and CSS transforms. I've already loaded an OBJ file into a Javascript class instance.
The model is triangulated. My first aim is just to display the triangles as planes (HTML elements are rectangular) - I'll be 'cutting out' the triangle shapes with CSS clip-path later on.
I am really struggling to understand and get the triangles of the model rotated correctly.
I thought a rotation matrix could help me out, but my only experience with those is where I already have the rotation vector and I need to convert and send that to WebGL. This time there is no WebGL (or tutorials) to make things easier.
The following excerpt shows the face creation/'rendering' of faces. I'm using the face normal as the rotation but I know this is wrong.
for (const face of _obj.faces) {
const vertices = face.vertices.map(_index => _obj.vertices[_index]);
const center = [
(vertices[0][0] + vertices[1][0] + vertices[2][0]) / 3,
(vertices[0][1] + vertices[1][1] + vertices[2][1]) / 3,
(vertices[0][2] + vertices[1][2] + vertices[2][2]) / 3
];
// Each vertex has a normal but I am just picking the first vertex' normal
// to use as the 'face normal'.
const normals = face.normals.map(_index => _obj.normals[_index]);
const normal = normals[0];
// HTML element creation code goes here; reference is 'element'.
// Set face position (unit space)
element.style.setProperty('--posX', center[0]);
element.style.setProperty('--posY', center[1]);
element.style.setProperty('--posZ', center[2]);
// Set face rotation, converting to degrees also.
const rotation = [
normal[0] * toDeg,
normal[1] * toDeg,
normal[2] * toDeg,
];
element.style.setProperty('--rotX', rotation[0]);
element.style.setProperty('--rotY', rotation[1]);
element.style.setProperty('--rotZ', rotation[2]);
}
The CSS first translates the face on X,Y,Z, then rotates it on X,Y,Z in that order.
I think I need to 'decompose' my triangles' rotation into separate axis rotations - i.e rotate on X, then on Y, then on Z to get the correct rotation as per the model face.
I realise that the normal vector gives me an orientation but not a rotation around itself - I need to calculate that. I think I have to determine a vector along one triangle side and cross it with the normal, but this is something I am not clear on.
I have spent hours looking at similar questions on SO but I'm not smart enough to understand or make them work for me.
Is it possible to describe what steps to take without Latex equations? I'm good with pseudo code but my Math skills are severely lacking.
The full code is here: https://whoshotdk.co.uk/cssfps/ (view HTML source)
The mesh building function is at line 422.
The OBJ file is here: https://whoshotdk.co.uk/cssfps/data/model/test.obj
The Blender file is here: https://whoshotdk.co.uk/cssfps/data/model/test.blend
The mesh is just a single plane at an angle, displayed in my example (wrongly) in pink.
The world is setup so that -X is left, -Y is up, -Z is into the screen.
Thank You!
If you have a plane and want to rotate it to be in the same direction as some normal, you need to figure out the angles between that plane's normal vector and the normal vector you want. The Euler angles between two 3D vectors can be complicated, but in this case the initial plane normal should always be the same, so I'll assume the plane normal starts pointing towards positive X to make the maths simpler.
You also probably want to rotate before you translate, so that everything is easier since you'll be rotating around the origin of the coordinate system.
By taking the general 3D rotation matrix (all three 3D rotation matrices multiplied together, you can find it on the Wikipedia page) and applying it to the vector (1,0,0) you can then get the equations for the three angles a, b, and c needed to rotate that initial vector to the vector (x,y,z). This results in:
x = cos(a)*cos(b)
y = sin(a)*cos(b)
z = -sin(b)
Then rearranging these equations to find a, b and c, which will be the three angles you need (the three values of the rotation array, respectively):
a = atan(y/x)
b = asin(-z)
c = 0
So in your code this would look like:
const rotation = [
Math.atan2(normal[1], normal[0]) * toDeg,
Math.asin(-normal[2]) * toDeg,
0
];
It may be that you need to use a different rotation matrix (if the order of the rotations is not what you expected) or a different starting vector (although you can just use this method and then do an extra 90 degree rotation if each plane actually starts in the positive Y direction, for example).

Passing PointLight Info to a custom Shader with three.js

I want to create an effect like the undulating sphere described in the Aerotwist Tutorial. However, in the tutorial Paul creates a fake GLSL hard-coded light in the fragment shader - instead I want to pass info from a three.js PointLight instance to my shaders, manipulate vertices/normals, then perform Phong shading.
My understanding of the various levels of GPU consideration when shading a scene in three.js is as follows (sticking with Phong, for example):
No GPU consideration: Use a MeshPhongMaterial and don't worry about shaders. This is super easy but doesn't let you mess around on the GPU side.
Some GPU consideration: Use a ShaderLib Phong shader. This allows you to push shading calculations to the GPU, but since they're pre-written you can't do any custom modification of vertex positions, normals, or illumination calculations.
Full GPU management: Use a ShaderMesh and write your shaders from scratch. This gives you full customization, but also forces you to explicitly pass the attributes and uniforms your shaders will need.
Q1: Is the above understanding accurate?
Q2: Is there a way to do something between levels 2 and 3? I want the ability to customize the shaders to mess with vertex positions/normals, but I don't want to write my own Phong shader when a perfectly good one is included with three.js.
Q3: If there is no such middle ground between levels 2 and 3, and I need to just go for level 3, whats the best way to go about it? Do I pass the light's position, intensity, etc. as uniforms, do my vertex/normal modifications, then finally explicitly write the Phong shading calculations?
It's very straightforward to do what you are asking with three.js
I'm not sure where it falls in your Q[]
Q1
You are still using the shaders, someone else wrote them for you. You only have access to the interface. Under the hood, calling something like MeshBasicMaterial can actually compile a different shader based on what you feed into it. Like, it may not process any UVS and not include them in the shader if there is no map called etc. You still have the power to impact the GPU depending on what you call.
If you are referring to the shader chunks, it's possible to hack stuff here, but it's pretty cumbersome. My advice is to study the code, for example the phong shading and start building your own piece by piece, using the chunks. Look at what goes in, what goes out.
No need to pass attributes. THREE.ShaderMaterial is not entirely built from scratch. It still provides you with quite a bit of stuff, and has a bunch of properties that you can set to get more. The basic attributes for one, are setup for you ie. you don't declare "attribute vec3 position". You can get an array containing all the lights in the scene if you tick the lighting flag as West illustrated, but you can ignore this if for example, you are building a particle shader, or some screen effect. Pretty much every shader is set up to read some basic attributes like 'position' 'uv' 'normal'. You can easily add your own on a procedural mesh, but on an actual model it's not trivial. You get some uniforms by default, you get the entire set of MVP matrices, 'cameraPosition' etc. Writing a phong shader from there is straightforward.
Now for how would you do this. Say that you are following this tutorial and you have this shader:
// same name and type as VS
varying vec3 vNormal;
void main() {
//this is hardcoded you want to pass it from your environment
vec3 light = vec3(0.5, 0.2, 1.0);//it needs to be a uniform
// ensure it's normalized
light = normalize(light);//you can normalize it outside of the shader, since it's a directional light
// calculate the dot product of
// the light to the vertex normal
float dProd = max(0.0,
dot(vNormal, light));
// feed into our frag colour
gl_FragColor = vec4(dProd, // R
dProd, // G
dProd, // B
1.0); // A
}
Here's what you need to do:
GLSL
uniform vec3 myLightPos;//comes in
void main(){
vec3 light = normalize(myLightPos);//but you better do this in javascript and just pass the normalized vec3
}
Javascript
new THREE.ShaderMaterial({
uniforms:{
myLightPos:{
type:"v3",
value: new THREE.Vector3()
}
},
vertexShader: yourVertShader,
fragmentShader: yourFragmentShader
});
Q1: Correct. Although, some users on this board have posted work-arounds for hacking MeshPhongMaterial, but that is not the original intent.
Q2 and Q3: Look at ShaderLib.js and you will see the "Normal Map Shader". This is a perfect template for you. Yes, you can duplicate/rename it and modify it to your liking.
It uses a Phong-based lighting model, and even accesses the scene lights for you. You call it like so:
var shader = THREE.ShaderLib[ "normalmap" ];
var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
. . .
var parameters = {
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: uniforms,
lights: true // set this flag and you have access to scene lights
};
var material = new THREE.ShaderMaterial( parameters );
See these examples: http://threejs.org/examples/webgl_materials_normalmap.html and http://threejs.org/examples/webgl_materials_normalmap2.html.
For coding patterns to follow, see ShaderLib.js and ShaderChunk.js.
three.js r.67

Double sided transparent shader looks buggy

I have made a little test that allows you to experiment with shaders in a 3D environment using three.js.
There's a sphere in the scene that shows the shader.
The demo shader I have created is a very simple shader that uses a 2D noise implementation. A big part of the sphere remains black, which I made transparent. I want the other side of the sphere to be visible too. So I have enabled transparency and set rendering side to double-sided.
material = new THREE.ShaderMaterial({
'uniforms': uniforms,
'fragmentShader': $('textarea#input-fragment').val(),
'vertexShader': $('textarea#input-vertex').val()
});
material.side = THREE.DoubleSide;
material.transparent = true;
On this example, the buggyness is easier to notice.
When the sphere is viewed from the top, you only see the shader from the outer side. When viewed from the side there seems to be a bit choppyness, and when viewed from the bottom it seems to be working.
These are the different angles (top - side - bottom):
Here's the important bit of my fragment shader:
void main() {
float r = cnoise(vNormal.yz * 2.0 + t);
float g = cnoise(vNormal.xz * -1.0 + t);
float b = cnoise(vNormal.xy * -2.0 + t);
// opacity ranges assumable from 0 - 3, which is OK
gl_FragColor = vec4(r, g, b, r + g + b);
}
So why am I seeing the choppy edges and why does the viewing angle matters?
There is nothing wrong with your shader. You can also see the effect if you set:
gl_FragColor = vec4( 1.0, 1.0, 1.0, 0.5 );
Self-transparency is tricky in three.js.
For performance reasons in WebGLRenderer, depth sorting works only between objects (based on their position), not within a single object.
The rendering order of the individual faces within an object cannot be controlled.
This is why from some viewing angles your scene looks better than from others.
One work-around is to explode the geometry into individual meshes of one face each.
Another work-around (your best bet, IMO) is to replace your transparent, double-sided sphere with two transparent spheres in the same location -- a front-sided one and a back-sided one.
three.js r.56
Very similar to what I ran into. The WHY to understand this is best explained on Three.js Transparency fundamentals.
Without more details on your code or goals, here is an alternate solution as of version r128. Just add one more line to your material:
material.depthTest: false,
in a nutshell, your shader is fine as #WestLangley mentioned, but during rendering transparency, the depth of pixels in relation to one another is taken into account as well - ending up in certain pixels not rendering. This is where your "buggy-ness" came from. Not really a bug, but the way your scene is rendered by default until told to do otherwise. There are a lot of *issues you can run into that compete with your expectations so I recommend reading up on the link I posted.
*One such issue: If there are other objects in your scene, then of course since you turned off depthTest you can get the incorrect object placement as an object that should be in the background can get rendered in the foreground.

Can a three.js material have separate repeat values for a bump map and a texture map?

I'm trying to break up the repetition in my texture by applying a bump map which repeats much less frequently. Unfortunately, it seems to take on the repeat value of 'landTexture' below (64), instead of the value I set it to (1).
landTexture.wrapS = landTexture.wrapT = THREE.RepeatWrapping;
landTexture.repeat.set(64, 64);
bumpTexture.wrapS = bumpTexture.wrapT = THREE.RepeatWrapping;
bumpTexture.repeat.set(1, 1);
var m = new THREE.MeshPhongMaterial({map:landTexture,
ambient: 0x552811,
specular: 0x333333,
shininess: 25,
bumpMap: bumpTexture,
bumpScale: 1,
metal: false });
If I comment out map:landTexture, then the bump map scale is 1. Can I mix these two repeat values somehow?
No. The offset and repeat values default to one of them:
// uv repeat and offset setting priorities
// 1. color map
// 2. specular map
// 3. displacement map
// 4. normal map
// 5. bump map
// 5. roughness map
// 5. metalness map
// 6. alpha map
// 7. emissive map
In your case, that would be the landTexture settings.
The workaround is to modify your textures, or create a custom ShaderMaterial.
EDIT: The exception is light map and ambient occlusion map, which each use the second set of UVs. This allows the other textures to be of higher detail than the light/AO map.
three.js r.84
Yes. In recent versions three.js r90^ has an API that can be used to change the behavior of built-in materials with GLSL.
It's not easy to do, but an example has been made:
https://github.com/pailhead/three.js/blob/aa72250835b82f7dde2e8375775a4b039cb719c6/examples/webgl_materials_extended_multiple_uvs.html
https://github.com/mrdoob/three.js/pull/14174
Basically the built in materials are based on shader templates, which is just an ordered list of #include <some_chunk> statements.
Some of these "chunks" contain some code that looks like this
/*...*/ texture2D( foo, vUv ) /*...*/
Where foo is alphaMap,map, specularMap etc. This means that a texture lookup is done on that sampler, at the interpolated uv attribute. You don't really care what precedes this code, or what follows it (it could be just a semi-colon ; or some mask .xy).
So what you want to do is apply some offset, or the way three.js does it, apply a mat3 transform.
The GLSL thus needs to look like this
texture2D( foo, foo_transform * vUv )
The problem then becomes supplying the shader with this uniform. The example does a bit of brute force by first compiling the shader, and then searching through the entire thing (otherwise you have to know in which chunks to look for this texture lookup).
This is a much better solution than modifying textures, and should actually be simpler than writing a custom ShaderMaterial.
Disclaimer - three is not really meant to be used like this but can be. So for example, while every map is prefixed somethingMap the albedo map is not and it's just called map, if it were an albedoMap the regular expression in this example would be simpler.

WebGL - Textured terrain with heightmap

I'm trying to create a 3D terrain using WebGL. I have a jpg with the texture for the terrain, and another jpg with the height values (-1 to 1).
I've looked at various wrapper libraries (like SpiderGL and Three.js), but I can't find a sutable example, and if I do (like in Three.js) the code is not documented and I can't figure out how to do it.
Can anyone give me a good tutorial or example?
There is an example at Three.js http://mrdoob.github.com/three.js/examples/webgl_geometry_terrain.html which is almost what I want. The problem is that they create the colour of the mountains and the height values randomly. I want to read these values from 2 different image files.
Any help would be appriciated.
Thanks
Check out this post over on GitHub:
https://github.com/mrdoob/three.js/issues/1003
The example linked there by florianf helped me to be able to do this.
function getHeightData(img) {
var canvas = document.createElement( 'canvas' );
canvas.width = 128;
canvas.height = 128;
var context = canvas.getContext( '2d' );
var size = 128 * 128, data = new Float32Array( size );
context.drawImage(img,0,0);
for ( var i = 0; i < size; i ++ ) {
data[i] = 0
}
var imgd = context.getImageData(0, 0, 128, 128);
var pix = imgd.data;
var j=0;
for (var i = 0, n = pix.length; i < n; i += (4)) {
var all = pix[i]+pix[i+1]+pix[i+2];
data[j++] = all/30;
}
return data;
}
Demo: http://oos.moxiecode.com/js_webgl/terrain/index.html
Two methods that I can think of:
Create your landscape vertices as a flat grid. Use Vertex Texture Lookups to query your heightmap and modulate the height (probably your Y component) of each point. This would probably be the easiest, but I don't think browser support for it is very good right now. (In fact, I can't find any examples)
Load the image, render it to a canvas, and use that to read back the height values. Build a static mesh based on that. This will probably be faster to render, since the shaders are doing less work. It requires more code to build the mesh, however.
For an example of reading image data, you can check out this SO question.
You may be interested in my blog post on the topic: http://www.pheelicks.com/2014/03/rendering-large-terrains/
I focus on how to efficiently create your terrain geometry such that you get an adequate level of detail in the near field as well as far away.
You can view a demo of the result here: http://felixpalmer.github.io/lod-terrain/ and all the code is up on github: https://github.com/felixpalmer/lod-terrain
To apply a texture to the terrain, you need to do a texture lookup in the fragment shader, mapping the location in space to a position in your texture. E.g.
vec2 st = vPosition.xy / 1024.0;
vec3 color = texture2D(uColorTexture, st)
Depending on your GLSL skills, you can write a GLSL vertex shader, assign the texture to one of your texture channels, and read the value in the vertex shader (I believe you need a modern card to read textures in a vertex shader but that may just be me showing my age :P )
In the vertex shader, translate the z value of the vertex based on the value read from the texture.
Babylon.js makes this extremely easy to implement. You can see an example at:
Heightmap Playground
They've even implemented the Cannon.js physics engine with it, so you can handle collisions: Heightmap with collisions
Note: as of this writing it only works with the cannon.js physics plugin, and friction doesn't work (must be set to 0). Also, make sure you set the location of a mesh/impostor BEFORE you set the physics state, or you'll get weird behavior.

Categories

Resources