Threejs: Sphere geometry not being shaded properly GLSL - three.js

I'm having a problem with a GLSL shader that interpolates color in 3D space, and assigns it based on the 3D coordinates of the bounding box and I can't seem to fix it:
The stamen in this codepen: https://codepen.io/ricky1280/pen/BaxyaZY
this is the code that I feel like probably has the problem, the geometry of the sphere:
const stamenEndCap = new THREE.SphereGeometry( sinCurveScale/120, 20, 20 );
// stamenEndCap.scale(1,1.5,1)
stamenEndCap.scale(4,1,1) //find a way to rotate geometry relative to the sin curve at the end
stamenEndCap.toNonIndexed();
stamenEndCap.computeBoundingSphere();
stamenEndCap.computeBoundingBox();
stamenEndCap.normalizeNormals();
stamenEndCap.computeTangents();
console.log(stamenEndCap.attributes.position.array)
for (var i=0; i<stamenEndCap.attributes.position.array.length; i=i+3){
stamenEndCap.attributes.position.array[i]=stamenEndCap.attributes.position.array[i]+((centerEnd.x)) //offset
stamenEndCap.attributes.position.array[i+1]=stamenEndCap.attributes.position.array[i+1]+((centerEnd.y))
stamenEndCap.attributes.position.array[i+2]=stamenEndCap.attributes.position.array[i+2]+((centerEnd.z)) //height?
}
stamenEndCap.computeVertexNormals();
// let positionVector = new THREE.Vector3(spherePoint.x,spherePoint.y,spherePoint.z)
// console.log(positionVector)
stamenEndCap.attributes.position.needsUpdate = true;
console.log(stamenEndCap.attributes.position.array)
let merge = THREE.BufferGeometryUtils.mergeBufferGeometries([geometry2,stamenEndCap])
merge.attributes.position.needsUpdate = true;
It is shaded improperly, it looks like this:
The color harshly changes from white to that light blue color on the vertical axis, even though the stamen end cap (line 364 of the codepen) is merged with the tube geometry and the shader is calculated across the 3D space of the entire merged object. The geometry becomes "merge" on line 394, and then "stamenGeom" on line 400. Then its boundingbox is used in the vertex and fragment shaders that exist on lines 422-552.
I'm not sure how to shade this properly so that it transitions smoothly, without the line denoting the change in color from white-blue. It doesn't seem to respond to normals, unfortunately.
Viewing the stamen from plan (top-down?) shows that the color is transitioning properly, but viewed from the side it appears as the image.
If anyone has any advice or solutions please let me know, and thank you for reading all of this.

figured it out: in the shader code the colors weren't being blended properly.
previous fragment shader code:
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
vec4 green = vec4(0.0, 1.0, 0.0, 1.0);
float f = clamp((vPos.z - bbMin.z) / (bbMax.z - bbMin.z)+vertOffset, 0., 1.);
// + is slider for vertical color position, -1 to 1
float linear_modifier = (1.00 * abs(1.) * f);
//vertical gradient position!!
//moves from 0-10?
vec3 col = mix(color1, color2, linear_modifier);
//float f2 = clamp((vPos.x - bbMin.x) / (bbMax.x - bbMin.x), 0., 1.);
float f2 = clamp(vUv.x, 0., 1.);
vec2 pos_ndc = vPos.xy*centerSize2;
float dist = length(pos_ndc*centerSize);
//controls central gradient position!
//the lower the larger?
//0-20
// float linear_modifier2 = (1.00 * abs(sin(1.0)) * dist);
//col = mix(color3, col, dist);
//NOT USING DIST REMOVES VERTICAL CENTRAL GRADIENT
// vec4 diffuseColor = vec4( col, opacity );
float f3 = clamp(vUv.x+f3Offset, 0., 1.);
// ^ THIS controls brightness of lowlights. lower the more intense.
col = mix(color3, col, f3);
//not using this removes LOWLIGHTS
//f3 is subtle fade
//col = mix(color3, col, f3);
//col = mix(color3, col, f2);
//f2 is default
vec4 diffuseColor = vec4( col, opacity );`
fixed shader code:
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`
vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
vec4 green = vec4(0.0, 1.0, 0.0, 1.0);
float f = clamp((vPos.z - bbMin.z) / (bbMax.z - bbMin.z)+vertOffset, 0., 1.);
// + is slider for vertical color position, -1 to 1
float linear_modifier = (1.00 * abs(1.) * f);
//vertical gradient position!!
//moves from 0-10?
vec3 col = mix(color1, color2, linear_modifier);
float f2 = clamp((vPos.x - bbMin.x) / (bbMax.x - bbMin.x), 0., 1.);
//float f2 = clamp(vUv.x, 0., 1.);
vec2 pos_ndc = vPos.xy*centerSize2;
float dist = length(pos_ndc*centerSize);
//controls central gradient position!
//the lower the larger?
//0-20
// float linear_modifier2 = (1.00 * abs(sin(1.0)) * dist);
//col = mix(color3, col, dist);
//NOT USING DIST REMOVES VERTICAL CENTRAL GRADIENT
// vec4 diffuseColor = vec4( col, opacity );
float f3 = clamp(vUv.x+f3Offset, 0., 1.);
// ^ THIS controls brightness of lowlights. lower the more intense.
//col = mix(color3, col, f3);
//not using this removes LOWLIGHTS
//f3 is subtle fade
//col = mix(color3, col, f3);
//col = mix(color3, col, f2);
//f2 is default
vec4 diffuseColor = vec4( col, opacity );
`

Related

How to get direction towards camera from vertex? (in a vertex shader, glsl)

I have this code:
vec4 localPosition = vec4( position, 1.);
vec4 worldPosition = modelMatrix * localPosition;
vec3 look = normalize( vec3(cameraPosition) - vec3(worldPosition) );
vec3 transformed = vec3( position ) + look;
But for some reason, it just moves the vertex 1 unit towards the origin point in the scene (0,0,0).
I need it to move the vertex towards the camera(where you are viewing the scene from).
I can't seem to find clear information anywhere on how to accomplish this.
It was a three.js issue.. Had to set the isShaderMaterial = true, in order to get the cameraPosition to update. o_o
material.isShaderMaterial = true; //We need to set this so that the cameraPosition uniform is updated in the shader
material.onBeforeCompile = function ( shader ) {
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
[
'float myOffset = 0.0;',
'myOffset = (vColor.r + vColor.g + vColor.b) < 3.0 ? 0.01 : 0.0;',
'vec4 localPosition = vec4( position, 1.);',
'vec4 worldPosition = modelMatrix * localPosition;',
'vec3 look = myOffset * normalize( cameraPosition - vec3(worldPosition) );',
'vec3 transformed = vec3( position ) + look;'
].join( '\n' )
);
material.userData.shader = shader;
};
if you have a view matrix, transform the vertex position to view coordinate and then you can do transformation according to the camera axis.

Convert ndc coordinates to world coordinates in fragment shader threejs

My goal is to draw a circle around my mouse cursor over a plane.
I get NDC coordinates (-1 to +1) that represent my cursor position:
const rect = targetHTML.getBoundingClientRect();
const mousePositionX = event.clientX - rect.left;
const mousePositionY = event.clientY - rect.top;
this._currentPoint = {
x: (mousePositionX / targetHTML.clientWidth * 2 - 1),
y: (mousePositionY / targetHTML.clientHeight * -2 + 1),
};
I pass it to my fragment shader via uniforms:
this._cursorMaterial.uniforms.uBrushPosition.value =
new window.THREE.Vector2(this._currentPoint.x, this._currentPoint.y);
In my fragment shader, I want to convert it to a world coordinate in order to compare it to the fragment world location.
// vertex shader
varying vec4 vPos;
void main() {
vPos = modelMatrix * vec4(position, 1.0 );
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0 );
}
// fragment shader
varying vec4 vPos;
uniform vec2 uBrushPosition;
void main() {
// convert uBrush position to world space
// uBrushPosition
vec3 brushWorldPosition = ?
//
if (distance(brushWorldPosition, vpos) < 10.) {
gl_FragColor = vec4(1., 0., 0., .5);
}
discard;
Not in the shader, but you can send it in as a uniform.
var mouseWorld = new THREE.Vector3( mouse.x, mouse.y, distanceFromCamera )
mouseWorld.unproject( camera )

Shader Z space perspective ShaderMaterial BufferGeometry

I'm changing the z coordinate vertices on my geometry but find that the Mesh Stays the same size, and I'm expecting it to get smaller. Tweening between vertex positions works as expected in X,Y space however.
This is how I'm calculating my gl_Position by tweening the amplitude uniform in my render function:
<script type="x-shader/x-vertex" id="vertexshader">
uniform float amplitude;
uniform float direction;
uniform vec3 cameraPos;
uniform float time;
attribute vec3 tweenPosition;
varying vec2 vUv;
void main() {
vec3 pos = position;
vec3 morphed = vec3( 0.0, 0.0, 0.0 );
morphed += ( tweenPosition - position ) * amplitude;
morphed += pos;
vec4 mvPosition = modelViewMatrix * vec4( morphed * vec3(1, -1, 0), 1.0 );
vUv = uv;
gl_Position = projectionMatrix * mvPosition;
}
</script>
I also tried something like this from calculating perspective on webglfundamentals:
vec4 newPos = projectionMatrix * mvPosition;
float zToDivideBy = 1.0 + newPos.z * 1.0;
gl_Position = vec4(newPos.xyz, zToDivideBy);
This is my loop to calculate another vertex set that I'm tweening between:
for (var i = 0; i < positions.length; i++) {
if ((i+1) % 3 === 0) {
// subtracting from z coord of each vertex
tweenPositions[i] = positions[i]- (Math.random() * 2000);
} else {
tweenPositions[i] = positions[i]
}
}
I get the same results with this -- objects further away in Z-Space do not scale / attenuate / do anything different. What gives?
morphed * vec3(1, -1, 0)
z is always zero in your code.
[x,y,z] * [1,-1,0] = [x,-y,0]

cocos2dx shader rotate a shape in fragment shader

This problem is cocos2d-x related since I am using cocos2d-x as game engine but I can think it can be solved use basic opengl shader knowledge.
Part 1:
. I have a canvas size of 800 * 600
. I try to draw a simple colored square in size of 96 * 96 which is placed in the middle of the canvas
It is quite simple, the draw part code :
var boundingBox = this.getBoundingBox();
var squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
var vertices = [
boundingBox.width, boundingBox.height,
0, boundingBox.height,
boundingBox.width, 0,
0, 0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.enableVertexAttribArray(cc.VERTEX_ATTRIB_POSITION);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(cc.VERTEX_ATTRIB_POSITION, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
And the vert shader:
attribute vec4 a_position;
void main()
{
gl_Position = CC_PMatrix * CC_MVMatrix * a_position;
}
And the frag shader:
#ifdef GL_ES
precision highp float;
#endif
uniform vec2 center;
uniform vec2 resolution;
uniform float rotation;
void main()
{
vec4 RED = vec4(1.0, 0.0, 0.0, 1.0);
vec4 GREEN = vec4(0.0, 1.0, 0.0, 1.0);
gl_FragColor = GREEN;
}
And everything works fine :
The grid line is size of 32 * 32, and the black dot indicates the center of the canvas.
Part 2:
. I try to separate the square into half (vertically)
. The left part is green and the right part is red
I changed the frag shader to get it done :
void main()
{
vec4 RED = vec4(1.0, 0.0, 0.0, 1.0);
vec4 GREEN = vec4(0.0, 1.0, 0.0, 1.0);
/*
x => [0, 1]
y => [0, 1]
*/
vec2 UV = (rotatedFragCoord.xy - center.xy + resolution.xy / 2.0) / resolution.xy;
/*
x => [-1, 1]
y => [-1, 1]
*/
vec2 POS = -1.0 + 2.0 * UV;
if (POS.x <= 0.0) {
gl_FragColor = GREEN;
}
else {
gl_FragColor = RED;
}
}
The uniform 'center' is the position of the square so it is 400, 300 in this case.
The uniform 'resolution' is the content size of the square so the value is 96, 96.
The result is fine :
Part 3:
. I try to change the rotation in cocos2dx style
myShaderNode.setRotation(45);
And the square is rotated but the content is not :
So I tried to rotate the content according to the rotation angle of the node.
I changed the frag shader again:
void main()
{
vec4 RED = vec4(1.0, 0.0, 0.0, 1.0);
vec4 GREEN = vec4(0.0, 1.0, 0.0, 1.0);
vec2 rotatedFragCoord = gl_FragCoord.xy - center.xy;
float cosa = cos(rotation);
float sina = sin(rotation);
float t = rotatedFragCoord.x;
rotatedFragCoord.x = t * cosa - rotatedFragCoord.y * sina + center.x;
rotatedFragCoord.y = t * sina + rotatedFragCoord.y * cosa + center.y;
/*
x => [0, 1]
y => [0, 1]
*/
vec2 UV = (rotatedFragCoord.xy - center.xy + resolution.xy / 2.0) / resolution.xy;
/*
x => [-1, 1]
y => [-1, 1]
*/
vec2 POS = -1.0 + 2.0 * UV;
if (POS.x <= 0.0) {
gl_FragColor = GREEN;
}
else {
gl_FragColor = RED;
}
}
The uniform rotation is the angle the node rotated so in this case it is 45.
The result is close to what I want but still not right:
I tried hard but just can not figure out what is wrong in my code and what's more if there is anyway easier to get things done.
I am quite new to shader programming and any advice will be appreciated, thanks :)

WebGL heightmap using vertex shader, using 32 bits instead of 8 bits

I'm using the following vertex shader (courtesy http://stemkoski.github.io/Three.js/Shader-Heightmap-Textures.html) to generate terrain from a grayscale height map:
uniform sampler2D bumpTexture;
uniform float bumpScale;
varying float vAmount;
varying vec2 vUV;
void main()
{
vUV = uv;
vec4 bumpData = texture2D( bumpTexture, uv );
vAmount = bumpData.r; // assuming map is grayscale it doesn't matter if you use r, g, or b.
// move the position along the normal
vec3 newPosition = position + normal * bumpScale * vAmount;
gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0);
}
I'd like to have 32-bits of resolution, and have generated a heightmap that encodes heights as RGBA. I have no idea how to go about changing the shader code to accommodate this. Any direction or help?
bumpData.r, .g, .b and .a are all quantities in the range [0.0, 1.0] equivalent to the original byte values divided by 255.0.
So depending on your endianness, a naive conversion back to the original int might be:
(bumpData.r * 255.0) +
(bumpdata.g * 255.0 * 256.0) +
(bumpData.b * 255.0 * 256.0 * 256.0) +
(bumpData.a * 255.0 * 256.0 * 256.0 * 256.0)
So that's the same as a dot product with the vector (255.0, 65280.0, 16711680.0, 4278190080.0), which is likely to be the much more efficient way to implement it.
With threejs
const generateHeightTexture = (width) => {
// let max_texture_width = RENDERER.capabilities.maxTextureSize;
let pixels = new Float32Array(width * width)
pixels.fill(0, 0, pixels.length);
let texture = new THREE.DataTexture(pixels, width, width, THREE.AlphaFormat, THREE.FloatType);
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.NearestFilter;
// texture.anisotropy = RENDERER.capabilities.getMaxAnisotropy();
texture.needsUpdate = true;
console.log('Built Physical Texture:', width, 'x', width)
return texture;
}

Resources