I have a simple model consisting of 1000 instances of sphere. I am trying to use instancing to reduce the number of draw calls. However, I am not able to change the transparency/opacity of the individual child geometries.
I already tried the following things:
I am able to change transparency of every sphere using
material.fragmentShader = "varying vec3 vColor;void main() { gl_FragColor = vec4( vColor, 0.2 );}";
However, that changes the opacity to 0.2 for every sphere.
The html file is like:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A-Frame Instancing component</title>
<meta name="description" content="A-Frame Instancing component">
<script>
/*
var WebVRConfig = {
FORCE_ENABLE_VR: true,
BUFFER_SCALE: 1.0
};
*/
</script>
<script src="https://cdn.rawgit.com/aframevr/aframe/v0.4.0/dist/aframe-master.min.js"></script>
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v3.2.0/dist/aframe-extras.min.js"></script>
<script type="text/javascript" src="build/aframe-instancing.js"></script>
</head>
<body>
<a-scene stats>
<a-assets>
<img id="sky" src="https://cdn.rawgit.com/aframevr/aframe/master/examples/primitives/models/peach-gradient.jpg">
</a-assets>
<a-entity instancing="count:100"></a-entity>
<a-sky src="#sky"></a-sky>
<a-entity light="type:directional;color:#FFFFFF" position="-1 1 1"></a-entity>
</a-scene>
</body>
</html>
The function to acheive instancing:
AFRAME.registerComponent('instancing', {
schema: {
count: {type: 'int', default: 10000}
},
var geometry = new THREE.InstancedBufferGeometry();
geometry.copy(new THREE.SphereBufferGeometry(5.0));
var translateArray = new Float32Array(count*3);
var vectorArray = new Float32Array(count*3);
var colorArray = new Float32Array(count*3);
geometry.addAttribute('translate', new THREE.InstancedBufferAttribute(translateArray, 3, 1));
geometry.addAttribute('vector', new THREE.InstancedBufferAttribute(vectorArray, 3, 1));
geometry.addAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 3, 1));
var material = new THREE.ShaderMaterial({
uniforms: {
time: {value: 0}
},
vertexShader: [
'attribute vec3 translate;',
'attribute vec3 vector;',
'attribute vec3 color;',
'uniform float time;',
'varying vec3 vColor;',
'const float g = 9.8 * 1.5;',
'void main() {',
' vec3 offset;',
' offset.xz = vector.xz * time;',
' offset.y = vector.y * time - 0.5 * g * time * time;',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position + translate + offset, 1.0 );',
' vColor = color;',
'}'
].join('\n'),
fragmentShader: [
'varying vec3 vColor;',
'void main() {',
' gl_FragColor = vec4( vColor, 1 );',
'}'
].join('\n')
});
var mesh = new THREE.Mesh(geometry, material);
this.model = mesh;
el.setObject3D('mesh', mesh);
el.emit('model-loaded', {format:'mesh', model: mesh});
//try to change opacity here
material.fragmentShader = "varying vec3 vColor;void main() { gl_FragColor = vec4( vColor, 0.2 );}";
material.transparent = true;
//use the new opacity
var mesh1 = new THREE.Mesh(geometry1, material);
this.mesh = mesh1;
el.setObject3D('mesh', mesh1);
el.emit('model-loaded', {format:'mesh', model: mesh1});
}
});
}
]);
Can anyone please tell me, how to change opacity of only one sphere? Thank you in advance!
Also, suppose I am trying to replicate multiple boxes. One of which is as following:
<a-box position="19.0 1.5 23.0"
width="32.0"
height="1.0"
depth="40.0"
color="#969696"
shader="flat"
flat-shading="true">
</a-box>
What would be the values I would fill in translateArray & vectorArray ? Thanks a lot in advance!
Your colors are only RGB
values, not RGBA
. Update your color
attribute to support 4 values, and change associated vec3
references to use vec4
. The last value in the vector will be your alpha
(transparency) value.
It looks like you already know how to send the value from your vertex shader
into your fragment shader
, so I won't go into detail there. But in your fragment shader
, you can use the color directly, because gl_FragColor
expects to be set to a vec4
.
Further clarification:
When you create a InstancedBufferAttribute
, you create one attribute per instance. So your color
attribute currently only carries RGB
values for each instance.
Hard-coding 1
or 0.2
in the w
place (i.e. gl_FragColor = vec4( vColor, 1 );
), you apply it universally to all instances. So, you need to define the alpha
value on a per-instance basis, and the easiest way to do this is through your already-created and instanced color
attribute.
//geometry.addAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 3, 1)
geometry.addAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 4, 1)
The code above makes room for the alpha values, which you should supply for each sphere. Your colorArray
will contain data like [ R, G, B, A, R, G, B, A, ... ]
.
Then, in your shaders...
// vertex shader
// ...
attribute vec4 color;
varying vec4 vColor;
// ...
void main(){
// ...
vColor = color;
// ...
}
// fragment shader
// ...
varying vec4 vColor;
// ...
void main(){
// ...
gl_FragColor = vColor;
}
Now, the alpha value that you supplied for each sphere instance will be used only for that instance. For example, if you want the sphere at index 1 to be the only transparent sphere, your colorArray
buffer should look like this:
colorArray = [
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.5,
1.0, 1.0, 1.0, 1.0,
// ...
];`
Important caveat
This implementation does not depth-sort the instances, and so blending will be dependent on render order. You can read more about this in the following question: