Cesium has solid arrows (PolylineArrow) and dashed lines (PolylineDash). I want to combine the two to make a PolylineDashArrow (An Arrow with a Dash Fill, or a dash line with an arrow head).
It sounds like this should be possible using Cesium's Fabric. Though I think I need to add a GLSL like the ones for arrow and dash. (The Fabric page doesn't say anything about how to add a custom GLSL to use for the source)
This seems like something that should be really easy to do but I can't find any information on anyone else trying to do this.
So, it should have been straightforward. But there's a small catch in that you don't want the dashes interrupting the arrow head itself. The arrow head should always be solid, or it looks wrong.
The biggest problem I ran into is that the dash material doesn't just mark the gaps between dashes as transparent, it actually marks them for discard. The good news is this is done with a Boolean value (not a raw discard keyword) that can be tricked into becoming false again, to keep those gaps from interrupting the arrow head.
So I had to cheat a bit to disable the dashMaterial's discard, but I got it to work.
Here's what I ended up with: Sandcastle demo of dashed arrow.
The code for that demo looks like this:
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
// Create sample polyline primitive.
var polylines = scene.primitives.add(new Cesium.PolylineCollection());
var polyline = polylines.add({
positions : Cesium.PolylinePipeline.generateCartesianArc({
positions : Cesium.Cartesian3.fromDegreesArray([-110.0, 42.0,
-85.0, 36.0,
-100.0, 25.0,
-77.0, 12.0])
}),
width : 15.0
});
// Assign a new fabric material blend of arrow and dashes.
polyline.material = new Cesium.Material({
fabric : {
materials : {
// The arrowMaterial provides the color and overall shape.
arrowMaterial : {
type : 'PolylineArrow',
uniforms : {
color : Cesium.Color.YELLOW
}
},
// The dashMaterial will punch holes in the arrowMaterial.
// Uniforms could be added to control the dash parameters.
dashMaterial : {
type : 'PolylineDash',
},
// "headMaterial" is copy-paste of the arrow head size code, written to alpha.
// It is used to mask out the dashes, to keep them from destroying the arrow head.
// A small tail is included behind the arrow head, to keep it from becoming a triangle.
headMaterial : {
source :
'czm_material czm_getMaterial(czm_materialInput materialInput) { \n' +
' czm_material material = czm_getDefaultMaterial(materialInput); \n' +
' vec2 st = materialInput.st; \n' +
'#ifdef GL_OES_standard_derivatives \n' +
// Original multiplier "10.0" changed to "15.0" to add short tail to head.
' float base = 1.0 - abs(fwidth(st.s)) * 15.0 * czm_pixelRatio; \n' +
'#else \n' +
' float base = 0.975; // 2.5% of the line will be the arrow head \n' +
'#endif \n' +
' material.alpha = 1.0 - smoothstep(base - 0.0001, base, st.s); \n' +
' return material; \n' +
'} \n'
}
},
// Finally, the "alpha" contains a cheat, where we undo czm_discard from the dashMaterial.
components : {
diffuse : 'arrowMaterial.diffuse',
alpha : 'arrowMaterial.alpha * (1.0 - (headMaterial.alpha * (1.0 - dashMaterial.alpha))); czm_discard = false'
}
}
});
A variant on emackey's answer. Alot more manual since it is merging the Arrow and Dash GLSL instead of using the simple Fabric mixing (And adding a line in the arrowhead logic to lock in the color used). This allows use of the dash multi-color effect. Here is the sandcastle demo, and code used below. (Note that this is still just a material primitive, and can't be used as a MaterialProperty in Entities)
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var PolylineDashArrowSource = '\
#ifdef GL_OES_standard_derivatives\n\
#extension GL_OES_standard_derivatives : enable\n\
#endif\n\
\n\
uniform vec4 color;\n\
uniform vec4 gapColor;\n\
uniform float dashLength;\n\
uniform float dashPattern;\n\
\n\
varying float v_polylineAngle;\n\
varying float v_width;\n\
\n\
const float maskLength = 16.0;\n\
\n\
mat2 rotate(float rad) {\n\
float c = cos(rad);\n\
float s = sin(rad);\n\
return mat2(\n\
c, s,\n\
-s, c\n\
);\n\
}\n\
\n\
float getPointOnLine(vec2 p0, vec2 p1, float x)\n\
{\n\
float slope = (p0.y - p1.y) / (p0.x - p1.x);\n\
return slope * (x - p0.x) + p0.y;\n\
}\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
vec2 pos = rotate(v_polylineAngle) * gl_FragCoord.xy;\n\
\n\
// Get the relative position within the dash from 0 to 1\n\
float dashPosition = fract(pos.x / (dashLength * czm_pixelRatio));\n\
// Figure out the mask index.\n\
float maskIndex = floor(dashPosition * maskLength);\n\
// Test the bit mask.\n\
float maskTest = floor(dashPattern / pow(2.0, maskIndex));\n\
vec4 fragColor = (mod(maskTest, 2.0) < 1.0) ? gapColor : color;\n\
\n\
vec2 st = materialInput.st;\n\
\n\
#ifdef GL_OES_standard_derivatives\n\
float base = 1.0 - abs(fwidth(st.s)) * 10.0 * czm_pixelRatio;\n\
#else\n\
float base = 0.975; // 2.5% of the line will be the arrow head\n\
#endif\n\
\n\
vec2 center = vec2(1.0, 0.5);\n\
float ptOnUpperLine = getPointOnLine(vec2(base, 1.0), center, st.s);\n\
float ptOnLowerLine = getPointOnLine(vec2(base, 0.0), center, st.s);\n\
\n\
float halfWidth = 0.15;\n\
float s = step(0.5 - halfWidth, st.t);\n\
s *= 1.0 - step(0.5 + halfWidth, st.t);\n\
s *= 1.0 - step(base, st.s);\n\
\n\
float t = step(base, materialInput.st.s);\n\
t *= 1.0 - step(ptOnUpperLine, st.t);\n\
t *= step(ptOnLowerLine, st.t);\n\
\n\
// Find the distance from the closest separator (region between two colors)\n\
float dist;\n\
if (st.s < base)\n\
{\n\
if (fragColor.a < 0.005) { // matches 0/255 and 1/255\n\
discard;\n\
}\n\
float d1 = abs(st.t - (0.5 - halfWidth));\n\
float d2 = abs(st.t - (0.5 + halfWidth));\n\
dist = min(d1, d2);\n\
}\n\
else\n\
{\n\
fragColor = color;\n\
float d1 = czm_infinity;\n\
if (st.t < 0.5 - halfWidth && st.t > 0.5 + halfWidth)\n\
{\n\
d1 = abs(st.s - base);\n\
}\n\
float d2 = abs(st.t - ptOnUpperLine);\n\
float d3 = abs(st.t - ptOnLowerLine);\n\
dist = min(min(d1, d2), d3);\n\
}\n\
\n\
vec4 outsideColor = vec4(0.0);\n\
vec4 currentColor = mix(outsideColor, fragColor, clamp(s + t, 0.0, 1.0));\n\
vec4 outColor = czm_antialias(outsideColor, fragColor, currentColor, dist);\n\
\n\
outColor = czm_gammaCorrect(outColor);\n\
material.diffuse = outColor.rgb;\n\
material.alpha = outColor.a;\n\
return material;\n\
}';
var PolylineDashArrowType = 'PolylineDashArrow';
Cesium.Material[PolylineDashArrowType] = PolylineDashArrowType;
Cesium.Material._materialCache.addMaterial(PolylineDashArrowType, {
strict: true,
fabric : {
type : PolylineDashArrowType,
uniforms: {
color : Cesium.Color.WHITE,
gapColor : Cesium.Color.TRANSPARENT,
dashLength : 16.0,
dashPattern : 255.0
},
source : PolylineDashArrowSource
},
translucent : true
});
var polylines = scene.primitives.add(new Cesium.PolylineCollection());
var polyline1 = polylines.add({
positions : Cesium.Cartesian3.fromDegreesArray([-110.0, 42.0,
-85.0, 36.0,
-100.0, 25.0,
-77.0, 12.0]),
width : 16,
material : Cesium.Material.fromType(PolylineDashArrowType, {
color: Cesium.Color.RED,
gapColor: Cesium.Color.TRANSPARENT
})
});
var polyline2 = polylines.add({
positions : Cesium.Cartesian3.fromDegreesArray([-130.0, 42.0,
-105.0, 36.0,
-120.0, 25.0,
-97.0, 12.0]),
width : 16,
material : Cesium.Material.fromType(PolylineDashArrowType, {
color: Cesium.Color.RED,
gapColor: Cesium.Color.YELLOW
})
});
And here is another version of it using Entities.
Related
I'm trying to import many transitions from GL Transitions into my video sequencer by converting GLSL to HLSL.
For example, this simple cross fade:
vec4 transition (vec2 uv) {
return mix(
getFromColor(uv),
getToColor(uv),
progress
);
}
is correctly translated in my HLSL code:
#define D2D_INPUT_COUNT 2
#define D2D_INPUT0_SIMPLE
#define D2D_INPUT1_SIMPLE
#define D2D_REQUIRES_SCENE_POSITION // The pixel shader requires the SCENE_POSITION input.
#include "d2d1effecthelpers.hlsli"
cbuffer constants : register(b0)
{
float progress : packoffset(c0.x);
...
}
float4 crossfade(float4 v1,float4 v2)
{
return lerp(v1, v2, progress);
}
D2D_PS_ENTRY(main)
{
float4 v1 = D2DGetInput(0);
float4 v2 = D2DGetInput(1);
return crossfade(v1,v2);
}
The same doesn't work for Wind effect:
// Custom parameters
uniform float size; // = 0.2
float rand (vec2 co) {
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
vec4 transition (vec2 uv) {
float r = rand(vec2(0, uv.y));
float m = smoothstep(0.0, -size, uv.x*(1.0-size) + size*r - (progress * (1.0 + size)));
return mix(
getFromColor(uv),
getToColor(uv),
m
);
}
This time HLSL is this:
float fract(float x)
{
return x - floor(x);
}
float rand(float2 co)
{
return fract(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453);
}
float4 wind(float4 v1, float4 v2,float2 uv)
{
float r = rand(float2(0, uv.y));
p1 = 0.2f;
progress = 0.5f; // hardcoded variables for testing, they will be taken from the buffer
float m = smoothstep(0.0f, -p1, uv.x*(1.0f-p1) + p1*r - (progress * (1.0f + p1)));
return lerp(v1, v2, m);
}
D2D_PS_ENTRY(main)
{
float4 v1 = D2DGetInput(0);
float4 v2 = D2DGetInput(1);
return wind(v1,v2,D2DGetScenePosition().xy);
}
Have I misunderstood the OpenGL's mix and fract and rand stuff? I only get the second image pixels in my HLSL version without mixing.
EDIT: I 've hardcoded size to 0.992 and multiplied progress by 4 in the HLSL. Now it seems to work, do I miss some bounds-related issues? Is the smoothstep function working as expected?
I found it.
It would need in main entry the usage of D2DGetInputCoordinate instead of D2DGetScenePosition
After doing that, the transitions run fine.
I have the a webgl blur shader:
precision mediump float;
precision mediump int;
uniform sampler2D u_image;
uniform float blur;
uniform int u_horizontalpass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma; // The sigma value for the gaussian function: higher value means more blur
// A good value for 9x9 is around 3 to 5
// A good value for 7x7 is around 2.5 to 4
// A good value for 5x5 is around 2 to 3.5
// ... play around with this based on what you need :)
varying vec4 v_texCoord;
const vec2 texOffset = vec2(1.0, 1.0);
// uniform vec2 texOffset;
const float PI = 3.14159265;
void main() {
vec2 p = v_texCoord.st;
float numBlurPixelsPerSide = blur / 2.0;
// Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
vec3 incrementalGaussian;
incrementalGaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;
vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
float coefficientSum = 0.0;
// Take the central sample first...
avgValue += texture2D(u_image, p) * incrementalGaussian.x;
coefficientSum += incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= numBlurPixelsPerSide; i += 1.0) {
avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;
avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
gl_FragColor = avgValue / coefficientSum;
}
When I build, I get the following error message:
webgl-renderer.js?2eb3:137 Uncaught could not compile shader:ERROR:
0:38: 'i' : Loop index cannot be compared with non-constant expression
I have also tried to use just the uniform float blur to compare i to. Is there any way to fix this?
The problem is further detailed here: https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php
The solution that I've found looking around is to only use a constant expression when comparing a loop var. This doesn't fit with what I need to do which is vary how many times I'm looping based on the blur radius.
Any thoughts on this?
This happens because on some hardware, GLSL loops are un-rolled into native GPU instructions. This means there needs to be a hard upper limit to the number of passes through the for loop, that governs how many copies of the loop's inner code will be generated. If you replace numBlurPixelsPerSide with a const float or even a #define directive, and the shader compiler can then determine the number of passes at compile time, and generate the code accordingly. But with a uniform there, the upper limit is not known at compile time.
There's an interesting wrinkle in this rule: You're allowed to break or call an early return out of a for loop, even though the max iterations must be discernible at compile time. For example, consider this tiny Mandelbrot shader. This is hardly the prettiest fractal on GLSL Sandbox, but I chose it for its small size:
precision mediump float;
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;
const float max_its = 100.;
float mandelbrot(vec2 z){
vec2 c = z;
for(float i=0.;i<max_its;i++){ // for loop is here.
if(dot(z,z)>4.) return i; // conditional early return here.
z = vec2(z.x*z.x-z.y*z.y,2.*z.x*z.y)+c;
}
return max_its;
}
void main( void ) {
vec2 p = surfacePosition;
gl_FragColor = vec4(mandelbrot(p)/max_its);
}
In this example, max_its is a const so the compiler knows the upper limit and can un-roll this loop if it needs to. Inside the loop, a return statement offers a way to leave the loop early for pixels that are outside of the Mandelbrot set.
You still don't want to set the max iterations too high, as this can produce a lot of GPU instructions and possibly hurt performance.
Try something like this:
const float MAX_ITERATIONS = 100.0;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= MAX_ITERATIONS; i += 1.0) {
if (i >= numBlurPixelsPerSide){break;}
avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;
avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
Sometimes you can use my very simple solving of issue.
My fragment of the shader source code:
const int cloudPointsWidth = %s;
for ( int i = 0; i < cloudPointsWidth; i++ ) {
//TO DO something
}
You can see '%' : syntax error above. But I am replace %s to a number in my javascript code before use my shader. For example:
vertexCode = vertexCode.replace( '%s', 10 );
vertexCode is my shader source code.
Everytime if I want to change cloudPointsWidth, I am destroying my old shader and creating new shader with new cloudPointsWidth .
Hope sometimes my solving can to help you.
You can just do a for loop with large constant number and use a break.
for(int i = 0; i < 1000000; ++i)
{
// your code here
if(i >= n){
break;
}
}
I've had similar problem with image downsampling shader. The code is basically the same:
for (int dx = -2 * SCALE_FACTOR; dx < 2 * SCALE_FACTOR; dx += 2) {
for (int dy = -2 * SCALE_FACTOR; dy < 2 * SCALE_FACTOR; dy += 2) {
/* accumulate fragment's color */
}
}
What I've ended up doing is using preprocessor and creating separate shader programs for every SCALE_FACTOR used (luckily, only 4 was needed). To achieve that, a small helper function was implemented to add #define ... statements to shader code:
function insertDefines (shaderCode, defines) {
var defineString = '';
for (var define in defines) {
if (defines.hasOwnProperty(define)) {
defineString +=
'#define ' + define + ' ' + defines[define] + '\n';
}
}
var versionIdx = shaderCode.indexOf('#version');
if (versionIdx == -1) {
return defineString + shaderCode;
}
var nextLineIdx = shaderCode.indexOf('\n', versionIdx) + 1;
return shaderCode.slice(0, nextLineIdx) +
defineString +
shaderCode.slice(nextLineIdx);
}
The implementation is a bit tricky because if the code already has #version preprocessor statement in it, all other statements have to follow it.
Then I've added a check for SCALE_FACROR being defined:
#ifndef SCALE_FACTOR
# error SCALE_FACTOR is undefined
#endif
And in my javascript code I've done something like this:
var SCALE_FACTORS = [4, 8, 16, 32],
shaderCode, // the code of my shader
shaderPrograms = SCALE_FACTORS.map(function (factor) {
var codeWithDefines = insertDefines(shaderCode, { SCALE_FACTOR: factor });
/* compile shaders, link program, return */
});
I use opengl es3 on android and solve this problem by using extension above the beginning of program like this:
#extension GL_EXT_gpu_shader5 : require
I don't know whether it work on webGL, but you can try it.
Hope it can help.
You can also use template litterals to set the length of the loop
onBeforeCompile(shader) {
const array = [1,2,3,4,5];
shader.uniforms.myArray = { value: array };
let token = "#include <begin_vertex>";
const insert = `
uniform float myArray[${array.length}];
for ( int i = 0; i < ${array.length}; i++ ) {
float test = myArray[ i ];
}
`;
shader.vertexShader = shader.vertexShader.replace(token, token + insert);
}
I have the a webgl blur shader:
precision mediump float;
precision mediump int;
uniform sampler2D u_image;
uniform float blur;
uniform int u_horizontalpass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma; // The sigma value for the gaussian function: higher value means more blur
// A good value for 9x9 is around 3 to 5
// A good value for 7x7 is around 2.5 to 4
// A good value for 5x5 is around 2 to 3.5
// ... play around with this based on what you need :)
varying vec4 v_texCoord;
const vec2 texOffset = vec2(1.0, 1.0);
// uniform vec2 texOffset;
const float PI = 3.14159265;
void main() {
vec2 p = v_texCoord.st;
float numBlurPixelsPerSide = blur / 2.0;
// Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
vec3 incrementalGaussian;
incrementalGaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;
vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
float coefficientSum = 0.0;
// Take the central sample first...
avgValue += texture2D(u_image, p) * incrementalGaussian.x;
coefficientSum += incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= numBlurPixelsPerSide; i += 1.0) {
avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;
avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
gl_FragColor = avgValue / coefficientSum;
}
When I build, I get the following error message:
webgl-renderer.js?2eb3:137 Uncaught could not compile shader:ERROR:
0:38: 'i' : Loop index cannot be compared with non-constant expression
I have also tried to use just the uniform float blur to compare i to. Is there any way to fix this?
The problem is further detailed here: https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php
The solution that I've found looking around is to only use a constant expression when comparing a loop var. This doesn't fit with what I need to do which is vary how many times I'm looping based on the blur radius.
Any thoughts on this?
This happens because on some hardware, GLSL loops are un-rolled into native GPU instructions. This means there needs to be a hard upper limit to the number of passes through the for loop, that governs how many copies of the loop's inner code will be generated. If you replace numBlurPixelsPerSide with a const float or even a #define directive, and the shader compiler can then determine the number of passes at compile time, and generate the code accordingly. But with a uniform there, the upper limit is not known at compile time.
There's an interesting wrinkle in this rule: You're allowed to break or call an early return out of a for loop, even though the max iterations must be discernible at compile time. For example, consider this tiny Mandelbrot shader. This is hardly the prettiest fractal on GLSL Sandbox, but I chose it for its small size:
precision mediump float;
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;
const float max_its = 100.;
float mandelbrot(vec2 z){
vec2 c = z;
for(float i=0.;i<max_its;i++){ // for loop is here.
if(dot(z,z)>4.) return i; // conditional early return here.
z = vec2(z.x*z.x-z.y*z.y,2.*z.x*z.y)+c;
}
return max_its;
}
void main( void ) {
vec2 p = surfacePosition;
gl_FragColor = vec4(mandelbrot(p)/max_its);
}
In this example, max_its is a const so the compiler knows the upper limit and can un-roll this loop if it needs to. Inside the loop, a return statement offers a way to leave the loop early for pixels that are outside of the Mandelbrot set.
You still don't want to set the max iterations too high, as this can produce a lot of GPU instructions and possibly hurt performance.
Try something like this:
const float MAX_ITERATIONS = 100.0;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= MAX_ITERATIONS; i += 1.0) {
if (i >= numBlurPixelsPerSide){break;}
avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;
avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
Sometimes you can use my very simple solving of issue.
My fragment of the shader source code:
const int cloudPointsWidth = %s;
for ( int i = 0; i < cloudPointsWidth; i++ ) {
//TO DO something
}
You can see '%' : syntax error above. But I am replace %s to a number in my javascript code before use my shader. For example:
vertexCode = vertexCode.replace( '%s', 10 );
vertexCode is my shader source code.
Everytime if I want to change cloudPointsWidth, I am destroying my old shader and creating new shader with new cloudPointsWidth .
Hope sometimes my solving can to help you.
You can just do a for loop with large constant number and use a break.
for(int i = 0; i < 1000000; ++i)
{
// your code here
if(i >= n){
break;
}
}
I've had similar problem with image downsampling shader. The code is basically the same:
for (int dx = -2 * SCALE_FACTOR; dx < 2 * SCALE_FACTOR; dx += 2) {
for (int dy = -2 * SCALE_FACTOR; dy < 2 * SCALE_FACTOR; dy += 2) {
/* accumulate fragment's color */
}
}
What I've ended up doing is using preprocessor and creating separate shader programs for every SCALE_FACTOR used (luckily, only 4 was needed). To achieve that, a small helper function was implemented to add #define ... statements to shader code:
function insertDefines (shaderCode, defines) {
var defineString = '';
for (var define in defines) {
if (defines.hasOwnProperty(define)) {
defineString +=
'#define ' + define + ' ' + defines[define] + '\n';
}
}
var versionIdx = shaderCode.indexOf('#version');
if (versionIdx == -1) {
return defineString + shaderCode;
}
var nextLineIdx = shaderCode.indexOf('\n', versionIdx) + 1;
return shaderCode.slice(0, nextLineIdx) +
defineString +
shaderCode.slice(nextLineIdx);
}
The implementation is a bit tricky because if the code already has #version preprocessor statement in it, all other statements have to follow it.
Then I've added a check for SCALE_FACROR being defined:
#ifndef SCALE_FACTOR
# error SCALE_FACTOR is undefined
#endif
And in my javascript code I've done something like this:
var SCALE_FACTORS = [4, 8, 16, 32],
shaderCode, // the code of my shader
shaderPrograms = SCALE_FACTORS.map(function (factor) {
var codeWithDefines = insertDefines(shaderCode, { SCALE_FACTOR: factor });
/* compile shaders, link program, return */
});
I use opengl es3 on android and solve this problem by using extension above the beginning of program like this:
#extension GL_EXT_gpu_shader5 : require
I don't know whether it work on webGL, but you can try it.
Hope it can help.
You can also use template litterals to set the length of the loop
onBeforeCompile(shader) {
const array = [1,2,3,4,5];
shader.uniforms.myArray = { value: array };
let token = "#include <begin_vertex>";
const insert = `
uniform float myArray[${array.length}];
for ( int i = 0; i < ${array.length}; i++ ) {
float test = myArray[ i ];
}
`;
shader.vertexShader = shader.vertexShader.replace(token, token + insert);
}
Is it possible to draw a Rect with rounded corners using a DrawNode object?
I think that something is possible using Bezier curves, but I have made some tries and I think I can't handle it.
Looking at API I've found only these 2 functions:
drawQuadBezier (const Vec2 &origin, const Vec2 &control, const Vec2 &destination, unsigned int segments, const Color4F &color)
drawCubicBezier (const Vec2 &origin, const Vec2 &control1, const Vec2 &control2, const Vec2 &destination, unsigned int segments, const Color4F &color)
[Modified after answer]
I have applied the answer in Cocos2dx, maybe somebody find this useful:
(just done some casting to int if you don't need high precision)
auto MagicConst = 0.552;
auto position = 150;
auto R = 50;
Vec2 TopLeft = Vec2(position, position + R * 2);
Vec2 TopRight = Vec2(position + R * 2, position + R * 2);
Vec2 BottomRight = Vec2(position + R * 2, position);
Vec2 BottomLeft = Vec2(position, position);
Vec2 originTL = Vec2(TopLeft.x, TopLeft.y - R);
Vec2 originTR = Vec2(TopRight.x - R, TopRight.y);
Vec2 originBR = Vec2(BottomRight.x - R, BottomRight.y);
Vec2 originBL = Vec2(BottomLeft.x, BottomLeft.y + R);
Vec2 control1TL = Vec2(TopLeft.x, (int) (TopLeft.y - R * (1 - MagicConst)));
Vec2 control1TR = Vec2((int) (TopRight.x - R * (1 - MagicConst)), TopRight.y);
Vec2 control1BR = Vec2((int) (BottomRight.x - R * (1 - MagicConst)), BottomRight.y);
Vec2 control1BL = Vec2(BottomLeft.x, (int) (BottomLeft.y + R * (1 - MagicConst)));
Vec2 control2TL = Vec2((int) (TopLeft.x + R * (1 - MagicConst)), TopLeft.y);
Vec2 control2TR = Vec2(TopRight.x, (int) (TopRight.y - R * (1 - MagicConst)));
Vec2 control2BR = Vec2(BottomRight.x, (int) (BottomRight.y + R * (1 - MagicConst)));
Vec2 control2BL = Vec2((int) (BottomLeft.x + R * (1 - MagicConst)), BottomLeft.y);
Vec2 destinationTL = Vec2(TopLeft.x + R, TopLeft.y);
Vec2 destinationTR = Vec2(TopRight.x, TopRight.y - R);
Vec2 destinationBR = Vec2(BottomRight.x, BottomRight.y + R);
Vec2 destinationBL = Vec2(BottomLeft.x + R, BottomLeft.y);
auto roundCorner = DrawNode::create();
roundCorner->drawCubicBezier(originTL, control1TL, control2TL, destinationTL, 10, Color4F::RED);
roundCorner->drawCubicBezier(originTR, control1TR, control2TR, destinationTR, 10, Color4F::GREEN);
roundCorner->drawCubicBezier(originBR, control1BR, control2BR, destinationBR, 10, Color4F::YELLOW);
roundCorner->drawCubicBezier(originBL, control1BL, control2BL, destinationBL, 10, Color4F::WHITE);
addChild(roundCorner);
This will produce: http://i.stack.imgur.com/mdEOM.png
Now you can change MagicConst to round the corners as you want.
For example with MagicConst = 0.9: http://i.stack.imgur.com/9V5cr.png
That is the result I wanted! ;) (thank you #Mbo)
(I can't post embedded image yet) :P
It is possible to calculate cubic Bezier curve that approximates a quarter of circle to make round corner.
Example for top left corner of axis-aligned rectangle (point TopLeft) and arc radius R:
Edit: Changed some -/+ signs
MagicConst = 0.552
Bezier.origin.X = ToplLeft.X
Bezier.origin.Y = ToplLeft.Y + R
Bezier.control1.X = ToplLeft.X
Bezier.control1.Y = ToplLeft.Y + R * (1-MagicConst)
Bezier.control2.X = ToplLeft.X + R * (1-MagicConst)
Bezier.control2.Y = ToplLeft.Y
Bezier.destination.X = ToplLeft.X + R
Bezier.destination.Y = ToplLeft.Y
about MagicConstant
You can easily find similar symmetric coordinates for other corners.
I did not consider case of extreme short rectangle edges (<2*R)
I have a vtkPolyData filled with points and cells that I want to draw on the screen. My polydata represents brain fibers (list of lines in 3D). A cell is a fiber. It's working, but I need to add colors between all points. We decided to color the polydata using a shader because there will be a lot of coloring methods. My vertex shader is:
vtkShader2 *shader = vtkShader2::New();
shader->SetType(VTK_SHADER_TYPE_VERTEX);
shader->SetSourceCode(R"VertexShader(
#version 120
attribute vec3 next_point;
varying vec3 vColor; // Pass to fragment shader
void main() {
float r = gl_Vertex.x - next_point.x;
float g = gl_Vertex.y - next_point.y;
float b = gl_Vertex.z - next_point.z;
if (r < 0.0) { r *= -1.0; }
if (g < 0.0) { g *= -1.0; }
if (b < 0.0) { b *= -1.0; }
const float norm = 1.0 / sqrt(r*r + g*g + b*b);
vColor = vec3(r * norm, g * norm, b * norm);
gl_Position = ftransform();
}
)VertexShader");
shader->SetContext(shader_program->GetContext());
shader_program->GetShaders()->AddItem(shader);
The goal here is, for each point, get the next point to calculate the color of the line between them. The problem is that I can't find a way to set the value of "next_point". I'm pretty sure it's always filled with 0.0 because the output image is red, blue and green on the sides.
I tried using vtkProperty::AddShaderVariable() but I never saw any change and the method's documentation hints about a "uniform variable" so it's probably not the right way.
// Splitted in 3 because I'm not sure how to pass a vtkPoints object to AddShaderVariable
fibersActor->GetProperty()->AddShaderVariable("next_x", nb_points, next_x);
fibersActor->GetProperty()->AddShaderVariable("next_y", nb_points, next_y);
fibersActor->GetProperty()->AddShaderVariable("next_z", nb_points, next_z);
I also tried using a vtkFloatArray filled with my points, then setting it as a data array.
vtkFloatArray *next_point = vtkFloatArray::New();
next_point->SetName("next_point");
next_point->SetNumberOfComponents(3);
next_point->Resize(nb_points);
// Fill next_point ...
polydata->GetPointData()->AddArray(next_point);
// Tried the vtkAssignAttribute class. Did nothing.
tl;dr Can you please tell me how to pass a list of points into a GLSL attribute variable? Thanks for your time.