I'm trying to wrap a SphereBufferGeometry with a ShaderMaterial where I'm using noise to resemble the surface of Jupiter, but it's wrapping very oddly to the sphere geometry. All of the animated texture appears in a thin belt around one of the lines of latitude rather than wrapped around the 'planet' like a normal texture. I've attached images below.
It works well on a plane, but I was probably naive to think it would simply wrap like a texture would wrap, and I'm quite new to Shader programming so I'm a bit stuck.
this is the plane which is wrapping fine
this is not wrapping correctly
I've a feeling that maybe I can move the noise equations to the fragmentShader - but my knowledge isn't there yet, it broke when I tried. I even tried morphing the targets of the plane into a sphere but ShaderMaterial doesn't natively support morphTargets and I after a LOT of trying to inject the #include <morphtarget_pars_vertex> using onBeforeCompile I still couldn't get it to work. I've also tried THREE's wrapping equations on the uniform texture, but it yields similar results.
Here's all of my code, the shaderMaterial implementation is in addPlanet():
import * as THREE from '../../build/three.module';
import { OrbitControls } from '../../examples/jsm/controls/OrbitControls';
const displacementVert = `
precision mediump float;
varying vec2 vUv;
varying float vWave;
uniform float uTime;
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x) {
return mod289(((x*34.0)+1.0)*x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
// x0 = x0 - 0.0 + 0.0 * C.xxx;
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
// Permutations
i = mod289(i);
vec4 p = permute( permute( permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
dot(p2,x2), dot(p3,x3) ) );
}
void main() {
vUv = uv;
vec3 pos = position;
float noiseFreq = 3.5;
float noiseAmp = 0.15;
vec3 noisePos = vec3(pos.x * noiseFreq + uTime, pos.y, pos.z);
pos.z += snoise(noisePos) * noiseAmp;
vWave = pos.z;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
}
`;
const displacementFrag = `
precision mediump float;
varying vec2 vUv;
varying float vWave;
uniform sampler2D uTexture;
void main() {
float wave = vWave * 0.25;
vec3 texture = texture2D(uTexture, vUv + wave).rgb;
gl_FragColor = vec4(texture, 1.);
}`;
let width, height;
let scene, camera, renderer;
let controls;
let wireframe;
let clock;
let planetShaderMaterial;
let jupiterSphere;
const init = ( params ) => {
colors = params.colors;
model = params.model;
width = params.width;
height = params.height;
scene = new THREE.Scene();
clock = new THREE.Clock();
camera = new THREE.PerspectiveCamera( params.fov, width / height, params.near, params.far );
camera.position.set( params.cameraPos.x, params.cameraPos.y, params.cameraPos.z );
renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true });
renderer.setSize( width, height );
renderer.outputEncoding = THREE.sRGBEncoding;
wireframe = params.wireframe;
renderer.render( scene, camera );
controls = new OrbitControls( camera, renderer.domElement );
addLights();
addPlanet();
}
const addLights = () => {
const ambientLight = new THREE.AmbientLight( 0xffffff, 10 );
scene.add( ambientLight );
const dir = 1024;
const light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 100, 100, 50 );
light.castShadow = true;
light.shadow.camera.left = -dir;
light.shadow.camera.right = dir;
light.shadow.camera.top = dir;
light.shadow.camera.bottom = -dir;
light.shadow.camera.near = 0.1;
light.shadow.camera.far = 1000;
light.shadow.mapSize.x = 1024;
light.shadow.mapSize.y = 1024;
scene.add( light );
}
// ******** HERE'S THE ShaderMaterial implementation
const addPlanet = () => {
const texture = new THREE.TextureLoader().load( './assets/textures/disp/jupiter.jpg' );
planetShaderMaterial = new THREE.ShaderMaterial( {
uniforms: {
uTime: { value: 0.0 },
uTexture: { value: texture }
},
wireframe: false,
side: THREE.FrontSide,
vertexShader: displacementVert,
fragmentShader: displacementFrag,
});
// these have no effect. Repeat Wrapping just repeats the current effect
planetShaderMaterial.uniforms.uTexture.value.wrapS = THREE.ClampToEdgeWrapping;
planetShaderMaterial.uniforms.uTexture.value.wrapT = THREE.ClampToEdgeWrapping;
jupiterSphere = new THREE.Mesh( new THREE.SphereBufferGeometry( 25, 32, 32), planetShaderMaterial );
scene.add( jupiterSphere );
}
const render = () => {
planetShaderMaterial.uniforms.uTime.value = clock.getElapsedTime();
renderer.render( scene, camera );
}
const resize = ( width, height ) => {
windowWidth = width;
windowHeight = height;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
const getRenderer = () => {
return renderer;
}
const TestWorld = {
init,
render,
resize,
getRenderer
};
export default TestWorld;
The problem probably lies in the magnitude of your uv displacement. This is essentially what your shader is doing:
vWave = pos.z;
float wave = vWave * 0.25;
vec3 texture = texture2D(uTexture, vUv + wave);
Your SphereGeometry has a radius of 25, so you're displacing your UVs by 25 * 0.25, based on their depth along the z-axis. This means you're getting UVs with a range of about [-6.25, 6.25].
You could re-calculate this value to be smaller (keep in mind that UVs are typically in the [0, 1] range, so a displacement of 6 will be far outside this range. Or, you could keep your UV displacement really large, and allow the texture to repeat with:
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
You can read about wrapping in the Texture docs page
Related
Here's an example based on the second live example from https://threejsfundamentals.org/threejs/lessons/threejs-shadertoy.html:
html, body {
height: 100%;
margin: 0;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
// Three.js - Shadertoy Basic
// from https://threejsfundamentals.org/threejs/threejs-shadertoy-basic.html
import * as THREE from 'https://unpkg.com/three#0.122.0/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
renderer.autoClearColor = false;
const camera = new THREE.PerspectiveCamera(
45, 16/9, 0.01, 1000
);
camera.position.z = 5
const scene = new THREE.Scene();
const plane = new THREE.PlaneBufferGeometry(2, 2);
const fragmentShader = `
#include <common>
uniform vec3 iResolution;
uniform float iTime;
// https://www.shadertoy.com/view/MtXSWj
float alternate(float p, float d){;
return sign(fract(p*d*.5)*2.-1.);
}
vec3 rainbow(float t){
return sin(t+vec3(0,.33,.66)*6.28)*.5+.5;
}
vec3 TwinDragon(vec2 p){
float time = fract(iTime*0.05)*20.;
//scaling
p = (p*2.-iResolution.xy)/iResolution.y*1.5;
//----------the fractal stuff---- ---THIS IS ANIMATIONS----(so remove them if you want)
p.y += alternate(p.x, 256. )/512. * clamp(time-16.,0.,2.)/2.;
p.x -= alternate(p.y, 128. )/256. * clamp(time-14.,0.,2.)/2.;
p.y += alternate(p.x, 64. )/128. * clamp(time-12.,0.,2.)/2.;
p.x -= alternate(p.y, 32. )/ 64. * clamp(time-10.,0.,2.)/2.;
p.y += alternate(p.x, 16. )/ 32. * clamp(time- 8.,0.,2.)/2.;
p.x -= alternate(p.y, 8. )/ 16. * clamp(time- 6.,0.,2.)/2.;
p.y += alternate(p.x, 4. )/ 8. * clamp(time- 4.,0.,2.)/2.;
p.x -= alternate(p.y, 2. )/ 4. * clamp(time- 2.,0.,2.)/2.;
// prettifying
vec2 block = ceil(p+.5); //index for blocks from which the fractal is shifted
vec3 color = rainbow(block.x*4.+block.y); //rainbow palette using block index as t
float dis = length(fract(p+.5)*2.-1.);//distance to middle of block
color *= .5+dis*.7; //using distance within block for some more pretty.
return color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ){
vec2 d = vec2(.5,0);
//some antialiasing
vec3 col = (
TwinDragon(fragCoord+d.xy)+
TwinDragon(fragCoord-d.xy)+
TwinDragon(fragCoord+d.yx)+
TwinDragon(fragCoord-d.yx)
)*.25;
fragColor = vec4(col,1.);
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3() },
};
const material = new THREE.ShaderMaterial({
fragmentShader,
uniforms,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(plane, material)
scene.add(mesh);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001; // convert to seconds
resizeRendererToDisplaySize(renderer);
const canvas = renderer.domElement;
uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
uniforms.iTime.value = time;
mesh.rotation.y += 0.01
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
But as you see, when I change to PerspectiveCamera, and rotate the plane, the texture does not transform with the object.
What's the simplest way to modify the example to make the shader transform with the plane, so that the graphic appears to be on the surface of the plane instead of the plane appearing to be like a mask?
The answer is to pass the uv coordinates from the vertex shader to the fragment shader using a varying variable.
Basically we can replace the lines
const fragment = `
... clipped ...
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3() },
};
const material = new THREE.ShaderMaterial({
fragmentShader,
with
const fragment = `
... clipped ...
varying vec2 vUv;
void main() {
mainImage(gl_FragColor, vUv * iResolution.xy);
}
`;
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3() },
};
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
and we get the following result:
html, body {
height: 100%;
margin: 0;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
// Three.js - Shadertoy Basic
// from https://threejsfundamentals.org/threejs/threejs-shadertoy-basic.html
import * as THREE from 'https://unpkg.com/three#0.122.0/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
renderer.autoClearColor = false;
const camera = new THREE.PerspectiveCamera(
45, 16/9, 0.01, 1000
);
camera.position.z = 5
const scene = new THREE.Scene();
const plane = new THREE.PlaneBufferGeometry(2, 2);
const fragmentShader = `
#include <common>
uniform vec3 iResolution;
uniform float iTime;
// https://www.shadertoy.com/view/MtXSWj
float alternate(float p, float d){;
return sign(fract(p*d*.5)*2.-1.);
}
vec3 rainbow(float t){
return sin(t+vec3(0,.33,.66)*6.28)*.5+.5;
}
vec3 TwinDragon(vec2 p){
float time = fract(iTime*0.05)*20.;
//scaling
p = (p*2.-iResolution.xy)/iResolution.y*1.5;
//----------the fractal stuff---- ---THIS IS ANIMATIONS----(so remove them if you want)
p.y += alternate(p.x, 256. )/512. * clamp(time-16.,0.,2.)/2.;
p.x -= alternate(p.y, 128. )/256. * clamp(time-14.,0.,2.)/2.;
p.y += alternate(p.x, 64. )/128. * clamp(time-12.,0.,2.)/2.;
p.x -= alternate(p.y, 32. )/ 64. * clamp(time-10.,0.,2.)/2.;
p.y += alternate(p.x, 16. )/ 32. * clamp(time- 8.,0.,2.)/2.;
p.x -= alternate(p.y, 8. )/ 16. * clamp(time- 6.,0.,2.)/2.;
p.y += alternate(p.x, 4. )/ 8. * clamp(time- 4.,0.,2.)/2.;
p.x -= alternate(p.y, 2. )/ 4. * clamp(time- 2.,0.,2.)/2.;
// prettifying
vec2 block = ceil(p+.5); //index for blocks from which the fractal is shifted
vec3 color = rainbow(block.x*4.+block.y); //rainbow palette using block index as t
float dis = length(fract(p+.5)*2.-1.);//distance to middle of block
color *= .5+dis*.7; //using distance within block for some more pretty.
return color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ){
vec2 d = vec2(.5,0);
//some antialiasing
vec3 col = (
TwinDragon(fragCoord+d.xy)+
TwinDragon(fragCoord-d.xy)+
TwinDragon(fragCoord+d.yx)+
TwinDragon(fragCoord-d.yx)
)*.25;
fragColor = vec4(col,1.);
}
varying vec2 vUv;
void main() {
mainImage(gl_FragColor, vUv * iResolution.xy);
}
`;
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3() },
};
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(plane, material)
scene.add(mesh);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001; // convert to seconds
resizeRendererToDisplaySize(renderer);
const canvas = renderer.domElement;
uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
uniforms.iTime.value = time;
mesh.rotation.y += 0.01
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
BLOOM AFFECTS TRANSPARENCY
For renderer I'm having this setup:
renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer:true, alpha:true } );
for bloom pass (post processing)
var renderPass = new RenderPass( scene, camera );
var bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );
bloomPass.exposure =0.2;
bloomPass.threshold =0;
bloomPass.strength = 0.2;
bloomPass.radius = 0.1;
composer.addPass( renderPass );
composer.addPass( bloomPass );
and while rendering I'm using
composer.render()
but this is affecting the transparency of the canvas by darkening it (Scene)
I had the same issue, your code is right for the creation of the UnrealBloomPass, but the issue is in the shader of the UnrealBloomPass at the method getSeperableBlurMaterial.
You need to replace the fragmentShader by this code below and your pass background will consider the alpha channel:
fragmentShader:
"#include <common>\
varying vec2 vUv;\n\
uniform sampler2D colorTexture;\n\
uniform vec2 texSize;\
uniform vec2 direction;\
\
float gaussianPdf(in float x, in float sigma) {\
return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\
}\
void main() {\n\
vec2 invSize = 1.0 / texSize;\
float fSigma = float(SIGMA);\
float weightSum = gaussianPdf(0.0, fSigma);\
float alphaSum = 0.0;\
vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\
for( int i = 1; i < KERNEL_RADIUS; i ++ ) {\
float x = float(i);\
float w = gaussianPdf(x, fSigma);\
vec2 uvOffset = direction * invSize * x;\
vec4 sample1 = texture2D( colorTexture, vUv + uvOffset);\
vec4 sample2 = texture2D( colorTexture, vUv - uvOffset);\
diffuseSum += (sample1.rgb + sample2.rgb) * w;\
alphaSum += (sample1.a + sample2.a) * w;\
weightSum += 2.0 * w;\
}\
gl_FragColor = vec4(diffuseSum/weightSum, alphaSum/weightSum);\n\
}"
A bloom pass is doing some mix between the image and a blurred version, causing colors to change. You should consider setting the WebGLRenderer tone mapping property to set a good color dynamic range
Tone mapping definition (Wikipedia)
Tone mapping is a technique used in image processing and computer
graphics to map one set of colors to another to approximate the
appearance of high dynamic range images in a medium that has a more
limited dynamic range.
Add this line in your init routine
renderer.toneMapping = THREE.ReinhardToneMapping
I'm trying to render a sphere in webgl, im using a method from webglfundamentals but somehow in my programm the sphere is just rendered to 20%. In chrome i get this error: L ERROR :GL_INVALID_OPERATION : glDrawArrays: attempt to access out of range vertices in attribute 1
var canvas;
var gl;
var index = 0;
var pointsArray = [];
var normalsArray = [];
var indexArray = [];
var colorArray = [];
var near = -10;
var far = 10;
var radius = 1.5;
var theta = 0.0;
var phi = 0.0;
var dr = 5.0 * Math.PI/180.0;
var left = -3.0;
var right = 3.0;
var ytop =3.0;
var bottom = -3.0;
var va = vec4(0.0, 0.0, -1.0,1);
var vb = vec4(0.0, 0.942809, 0.333333, 1);
var vc = vec4(-0.816497, -0.471405, 0.333333, 1);
var vd = vec4(0.816497, -0.471405, 0.333333,1);
var lightPosition = vec4(0.0, 1.0, 1.0, 0.0 );
var lightAmbient = vec4(0.2, 0.2, 0.2, 1.0 );
var lightDiffuse = vec4( 1.0, 1.0, 1.0, 1.0 );
var lightSpecular = vec4( 1.0, 1.0, 1.0, 1.0 );
var materialAmbient = vec4( 1.0, 0.0, 1.0, 1.0 );
var materialDiffuse = vec4( 1.0, 0.0, 0.0, 1.0 );
var materialSpecular = vec4( 1.0, 0.8, 0.0, 1.0 );
var materialShininess = 100.0;
var ctm;
var ambientColor, diffuseColor, specularColor;
var texture;
var modelViewMatrix, projectionMatrix;
var modelViewMatrixLoc, projectionMatrixLoc;
var eye;
var at = vec3(0.0, 0.0, 0.0);
var up = vec3(0.0, 1.0, 0.0);
var iBuffer;
function createSphereVertices(
radius,
subdivisionsAxis,
subdivisionsHeight,
opt_startLatitudeInRadians,
opt_endLatitudeInRadians,
opt_startLongitudeInRadians,
opt_endLongitudeInRadians) {
if (subdivisionsAxis <= 0 || subdivisionsHeight <= 0) {
throw Error('subdivisionAxis and subdivisionHeight must be > 0');
}
opt_startLatitudeInRadians = opt_startLatitudeInRadians || 0;
opt_endLatitudeInRadians = opt_endLatitudeInRadians || Math.PI;
opt_startLongitudeInRadians = opt_startLongitudeInRadians || 0;
opt_endLongitudeInRadians = opt_endLongitudeInRadians || (Math.PI * 2);
var latRange = opt_endLatitudeInRadians - opt_startLatitudeInRadians;
var longRange = opt_endLongitudeInRadians - opt_startLongitudeInRadians;
// We are going to generate our sphere by iterating through its
// spherical coordinates and generating 2 triangles for each quad on a
// ring of the sphere.
// var numVertices = (subdivisionsAxis + 1) * (subdivisionsHeight + 1);
var positions = [];
// var normals = webglUtils.createAugmentedTypedArray(3, numVertices);
// var texCoords = webglUtils.createAugmentedTypedArray(2 , numVertices);
// Generate the individual vertices in our vertex buffer.
for (var y = 0; y <= subdivisionsHeight; y++) {
for (var x = 0; x <= subdivisionsAxis; x++) {
// Generate a vertex based on its spherical coordinates
var u = x / subdivisionsAxis;
var v = y / subdivisionsHeight;
var theta = longRange * u;
var phi = latRange * v;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
var sinPhi = Math.sin(phi);
var cosPhi = Math.cos(phi);
var ux = cosTheta * sinPhi;
var uy = cosPhi;
var uz = sinTheta * sinPhi;
positions.push(vec4(radius * ux, radius * uy, radius * uz,1.0));
normalsArray.push(vec4(ux, uy, uz,1.0));
// texCoords.push(1 - u, v);
}
}
var numVertsAround = subdivisionsAxis + 1;
// var indices = webglUtils.createAugmentedTypedArray(3, subdivisionsAxis * subdivisionsHeight * 2, Uint16Array);
for (var x = 0; x < subdivisionsAxis; x++) {
for (var y = 0; y < subdivisionsHeight; y++) {
// Make triangle 1 of quad.
pointsArray.push(positions[(y + 0) * numVertsAround + x]);
pointsArray.push(positions[(y + 0) * numVertsAround + x + 1]);
pointsArray.push(positions[(y + 1) * numVertsAround + x]);
// Make triangle 2 of quad.
pointsArray.push(positions[(y + 1) * numVertsAround + x]);
pointsArray.push(positions[(y + 0) * numVertsAround + x + 1]);
pointsArray.push(positions[(y + 1) * numVertsAround + x + 1]);
index +=6;
}
}
}
window.onload = function init() {
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.enable(gl.DEPTH_TEST);
//
// Load shaders and initialize attribute buffers
//
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
createSphereVertices(1,12,12);
ambientProduct = mult(lightAmbient, materialAmbient);
diffuseProduct = mult(lightDiffuse, materialDiffuse);
specularProduct = mult(lightSpecular, materialSpecular);
var nBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, nBuffer);
gl.bufferData( gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW );
var vNormal = gl.getAttribLocation( program, "vNormal" );
gl.vertexAttribPointer( vNormal, 4, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vNormal);
var vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);
var vPosition = gl.getAttribLocation( program, "vPosition");
gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
modelViewMatrixLoc = gl.getUniformLocation( program, "modelViewMatrix" );
projectionMatrixLoc = gl.getUniformLocation( program, "projectionMatrix" );
gl.uniform4fv( gl.getUniformLocation(program,
"ambientProduct"),flatten(ambientProduct) );
gl.uniform4fv( gl.getUniformLocation(program,
"diffuseProduct"),flatten(diffuseProduct) );
gl.uniform4fv( gl.getUniformLocation(program,
"specularProduct"),flatten(specularProduct) );
gl.uniform4fv( gl.getUniformLocation(program,
"lightPosition"),flatten(lightPosition) );
gl.uniform1f( gl.getUniformLocation(program,
"shininess"),materialShininess );
render();
}
function render() {
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
eye = vec3(radius*Math.sin(theta)*Math.cos(phi),
radius*Math.sin(theta)*Math.sin(phi), radius*Math.cos(theta));
modelViewMatrix = lookAt(eye, at , up);
projectionMatrix = ortho(left, right, bottom, ytop, near, far);
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix) );
gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix) );
for( var i=0; i<index; i+=3)
gl.drawArrays( gl.TRIANGLES, i, 3);
window.requestAnimFrame(render);
}
project1.html
<!DOCTYPE html>
<html>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = -(modelViewMatrix * vPosition).xyz;
vec3 light = lightPosition.xyz;
L = normalize( light - pos );
E = -pos;
N = normalize( (modelViewMatrix*vNormal).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;
void main()
{
vec4 fColor;
vec3 H = normalize( L + E );
vec4 ambient = ambientProduct;
float Kd = max( dot(L, N), 0.0 );
vec4 diffuse = Kd*diffuseProduct;
float Ks = pow( max(dot(N, H), 0.0), shininess );
vec4 specular = Ks * specularProduct;
if( dot(L, N) < 0.0 ) specular = vec4(0.0, 0.0, 0.0, 1.0);
fColor = ambient + diffuse +specular;
fColor.a = 1.0;
gl_FragColor = fColor;
}
</script>
<script type="text/javascript" src="./Common/webgl-utils.js"></script>
<script type="text/javascript" src="./Common/initShaders.js"></script>
<script type="text/javascript" src="./Common/MV.js"></script>
<script type="text/javascript" src="project1.js"></script>
<body>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
</html>
This is what i get only a unfinished sphere:
I don't know this is actual correct answer since I didn't check this by running codes.
But, I just guess why this error occured.
Major reason of "out of range in attribute variable"
Basically, this will happen when the buffer you passed didn't have enough length.
Therefore, you need to check the count of buffer source you created.
And your error is ":GL_INVALID_OPERATION : glDrawArrays: attempt to access out of range vertices in attribute 1". In this case, attribute 1 in the error message means "vNormal" attribute in your GLSL code.
Because it is 2nd attribute variable in your GLSL code.
(If the error code says attribute 0, this problem should be made by vPosition)
As long as I saw your code, I think there is mismatch of length with normal buffer and position buffer.
Surface count in your code : Subdivision Height * Subdivision Axis * 2
Position element count in your code:Subdivision Height * Subdivision Axis *6
Normal element count in your code: Subdivision Height * Subdivision Axis * 4
I guess this is the reason of the problem.
Each vertex must have position and normal in this case, so your normal element count must be Subdivision Height * Subdivision Axis * 6
2nd argument in gl.vertexAttribPointer
I think you have mistake about 2nd argument in gl.vertexAttribPointer.
This is the count which means how many float elements needs to be passed each vertex.
In this case, you pushed 3 float elements for each vertex. So, you needs to specify 3 as argument for vPosition even if you used vec4 in your GLSL code.
By this argument, GPU can split these buffers for each vertex and pass them into vertex shader parallely.
4th element in vector
This is not strongly related to your question.But I found the code can be a reason of bug.
positions.push(vec4(radius * ux, radius * uy, radius * uz,1.0));
normalsArray.push(vec4(ux, uy, uz,1.0));
You need to understand what the meaning of 4th element in vec4. When this vector means coordinates, 4th element must be 1.However, when this vector means directions, 4th element must be 0.
Because, if this means directions, direction can not be affected by translation transform. ( I suggest you to learn affine transform to understand this)
So, you need to rewrite that code like this.
positions.push(vec4(radius * ux, radius * uy, radius * uz,1.0));
normalsArray.push(vec4(ux, uy, uz,0));
I hope this answer could help you...
I've created a custom shader in three.js to allow me to set individual vertices color, size and position.
Color and size work fine but when I update the vertex x,y or z it's not updated on screen.
JSFiddle
What am I missing?
Vertex shaders:
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
attribute vec3 color;
varying vec3 vColor;
varying vec2 vUv;
void main()
{
vColor = color;
gl_PointSize = size;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
Fragment shader
<script type="x-shader/x-fragment" id="fragmentshader">
uniform sampler2D texture;
varying vec2 vUv;
varying vec3 vColor;
void main()
{
vec4 color = vec4(vColor, 1);
gl_FragColor = color;
}
</script>
JS:
var container = document.getElementById('container');
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 100;
var scene = new THREE.Scene();
var vShader = document.getElementById('vertexshader').textContent;
var fShader = document.getElementById('fragmentshader').textContent;
var uniforms = {};
var attributes = {
color: { type: 'c', value: [] },
size: { type: 'f', value: [] }
};
var geometry = new THREE.Geometry();
for ( var i = 0; i < 100; i ++ )
{
var angle = Math.random() * Math.PI * 2;
var radius = 40 + (Math.random() * 5);
var vertex = new THREE.Vector3();
vertex.x = Math.cos(angle) * radius;
vertex.y = Math.sin(angle) * radius;
vertex.z = 0;
attributes.size.value[i] = Math.random() * 10;
attributes.color.value[i] = new THREE.Color( 0xff0000 );
geometry.vertices.push( vertex );
}
var material = new THREE.ShaderMaterial(
{
uniforms: uniforms,
attributes: attributes,
vertexShader: vShader,
fragmentShader: fShader,
transparent: true
});
var particleSystem = new THREE.PointCloud(geometry, material);
scene.add( particleSystem );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x292B30, 1 );
container.appendChild( renderer.domElement );
animate();
function animate()
{
requestAnimationFrame( animate );
render();
}
function render()
{
for (var i = geometry.vertices.length - 1; i >= 0; i--)
{
geometry.vertices[i].z = geometry.vertices[i].z - 0.5 ;
}
camera.lookAt( scene.position );
//particleSystem.geometry.__dirtyVertices = true;
renderer.render( scene, camera );
}
When modifying the vertex position, set the verticesNeedUpdate flag:
geometry.verticesNeedUpdate = true;
Updated fiddle: https://jsfiddle.net/1cmg0z2f/2/
Three.js r71, due to changes in ShaderMaterial the fiddle doesnt work with the new version.
I added a comment to the previous accepted answer but just in case here's an updated answer
geometry.attributes[attributeName].needsUpdate = true
eg
geometry.attributes.position.needsUpdate = true
keep in mind that this works for custom / shader attributes that you've added using setAttribute, which is what I was searching for before I found this QA.
I'm trying to implement the Cook-Torrance shading algorithm in three.js I have a mostly working solution, however it doesn't show the effects of the ambient light. The sides of the cube not illuminated by the light are completely black. If I remove the "Beckmann term" then I can indeed see the ambient light effect:
While, replacing Beckmann with the function that always return 0.0 I get:
It seems like the cause of the wrong behaviour is the division in:
vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
If I modify NdotL * NdotV to NdotV and change the computation for gl_FragColor to:
gl_FragColor = vec4(beta * NdotL * (1.0-s)*Kd + beta * s*Specular + ambient*Kd, 1.0);
Everything seems to work correctly.
What I don't understand is: why? This problem with the division isn't mentioned anywhere, and I'm not 100% sure that even the remaining division wont cause problems in other situations.
Here's the full MWE:
<html>
<head>
<title>Cook-Torrance BRDF computed by shader</title>
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
</style>
<script src="lib/three.min.js"></script>
<script src="lib/OrbitControls.js"></script>
</head>
<body>
<script type="text/x-glsl" id="vertex">
varying vec3 transformedNormal;
varying vec3 pointPosition;
varying vec3 lightVector;
uniform vec3 pointLightPosition;
void main()
{
transformedNormal = normalMatrix * normal;
pointPosition = (modelViewMatrix * vec4( position, 1.0 )).xyz;
vec4 lPosition = viewMatrix * vec4( pointLightPosition, 1.0 );
lightVector = lPosition.xyz - pointPosition;
gl_Position = projectionMatrix * vec4(pointPosition,1.0);
}
</script>
<script type="text/x-glsl" id="ct-fragment">
uniform vec3 lightPower;
uniform vec3 ambient;
uniform vec3 Kd; // surface diffuse color
uniform vec3 Ks; // surface specular color: equal to R_F(0)
uniform float m; // material roughness (average slope of microfacets)
uniform float s; // percentage of incoming light which is specularly reflected
varying vec3 transformedNormal;
varying vec3 pointPosition;
varying vec3 lightVector;
#define PI 3.14159265
float G(float NdotH, float NdotV, float VdotH, float NdotL)
{
float G1 = 2.0 * NdotH * NdotV / VdotH;
float G2 = 2.0 * NdotH * NdotL / VdotH;
return min( 1.0, min( G1, G2 ));
}
vec3 R_F(float VdotH) {
return Ks + (1.0 - Ks)*pow(1.0-VdotH, 5.0);
}
float Beckmann(float NdotH){
float A = 1.0 / (pow(m,2.0)+pow(NdotH,4.0)*PI);
float B = exp( - pow( tan(acos(NdotH)) , 2.0) / pow(m,2.0));
return A*B;
}
void main()
{
vec3 n = normalize( transformedNormal );
vec3 v = normalize( -pointPosition );
vec3 l = normalize( lightVector );
vec3 h = normalize( v+l );
float NdotH = max(0.0, dot( n, h ));
float VdotH = max(0.0, dot( v, h ));
float NdotV = max(0.0, dot( n, v ));
float NdotL = max(0.0, dot( n, l ));
// specular BRDF
vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
vec3 beta = lightPower / ( 4.0 * PI * pow( length(lightVector),2.0) );
gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*Specular) + ambient*Kd, 1.0);
}
</script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position = new THREE.Vector3(0,0,5);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xf0f0f0 );
document.body.appendChild( renderer.domElement );
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
var uniforms = {
Ks: { type: "v3", value: new THREE.Vector3() },
Kd: { type: "v3", value: new THREE.Vector3() },
ambient: { type: "v3", value: new THREE.Vector3() },
pointLightPosition: { type: "v3", value: new THREE.Vector3() },
lightPower: { type: "v3", value: new THREE.Vector3() },
s: {type: "f", value: 0},
m: {type: "f", value: 0}
};
var vs = document.getElementById("vertex").textContent;
var fs = document.getElementById("ct-fragment").textContent;
var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vs, fragmentShader: fs });
var geometry = new THREE.CubeGeometry(1, 1, 1);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
light = new THREE.Mesh( new THREE.SphereGeometry( 1, 16, 16), new THREE.MeshBasicMaterial ({color: 0xffff00, wireframe:true}));
light.position = new THREE.Vector3( 10.0, 10.0, 10.0 );
scene.add( light );
uniforms.Ks.value = new THREE.Vector3( 0.95, 0.93, 0.88 );
uniforms.Kd.value = (new THREE.Vector3( 0.50754, 0.50754, 0.50754 ));
uniforms.ambient.value = (new THREE.Vector3( 0.5, 0.5, 0.5 ));
uniforms.pointLightPosition.value = new THREE.Vector3(light.position.x, light.position.y, light.position.z);
uniforms.lightPower.value = new THREE.Vector3( 7000.0, 7000.0, 7000.0 );
uniforms.s.value = 0.5;
uniforms.m.value = 0.1;
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
controls.update();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
The shading equation is a mathematical description of the Cook-Torrance shading model.
Writing an actual shader is a different thing that should take into account the fact that not all operations between floats have the same properties of the real mathematical operations in the equation.
In this case diving by 0 causes problems. In fact the problem is that the definition of Specular is diving by 0, but when assigning to gl_FragColor I'm multiplying again by NdotL obtaining 0 * inf = NaN, and it seems like NaN is interpreted as a zero/negative number by the GPU (thus displaying black).
As a reference, the correct main() is:
void main()
{
vec3 n = normalize( transformedNormal );
vec3 v = normalize( -pointPosition );
vec3 l = normalize( lightVector );
vec3 h = normalize( v+l );
vec3 specular = vec(0.0, 0.0, 0.0);
float NdotH = max(0.0, dot( n, h ));
float VdotH = max(0.0, dot( v, h ));
float NdotV = max(0.0, dot( n, v ));
float NdotL = max(0.0, dot( n, l ));
if (NdotL > 0 && NdotV > 0)
{
specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
}
vec3 beta = lightPower / ( 4.0 * PI * pow( length(lightVector),2.0) );
gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*specular) + ambient*Kd, 1.0);
}