Looking for DrawCommand rendering in 2D/Columbus view

In the following DrawCommand code, a red triangle is properly rendered at the right position, Washinton, DC.
But when changing the view from 3D to 2D or Columbus, the red triangle is rendered at wrong position, mid of Atlantic Ocean, and the triangle shape is distorted.

View in 3DView in 3D

View in 2D
DrawCommand_2D

View in Columbus
DrawCommand_Columbus

I guess the reason is the transformation matrix used in the vertex shader(GLSL), czm_modelViewProjection.
It should be changed to something else for 2D/Columbus view, but I have no idea.

I have looked for a sample code using DrawCommand and work well both in 3D and 2D, but as far now, I can’t find anything.

Your kind advice would be highly appreciated.

var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;

const basePosition = [ -77., 39.];  // Washington, DC
let sizing = 10.
let position0 = Cesium.Cartesian3.fromDegrees(basePosition[0]-sizing, basePosition[1]-(sizing/2.), 0);
let position1 = Cesium.Cartesian3.fromDegrees(basePosition[0]   , basePosition[1]+(sizing/2.), 0);
let position2 = Cesium.Cartesian3.fromDegrees(basePosition[0]+sizing, basePosition[1]-(sizing/2.), 0);
let positions = [
    position0.x,position0.y,position0.z,
    position1.x,position1.y,position1.z,
    position2.x,position2.y,position2.z];

scene.primitives.add(new MyPrimitive());

function MyPrimitive() {
    this.drawCommand = undefined;
}

MyPrimitive.prototype.update = function(frameState) {
    if (Cesium.defined(this.drawCommand)) {
        frameState.commandList.push(this.drawCommand);
    }
    var context = frameState.context;
    var vertexBuffer = Cesium.Buffer.createVertexBuffer({
        context: context,
        typedArray: new Float32Array(positions),
        usage: Cesium.BufferUsage.STATIC_DRAW
    });

    var indexBuffer = Cesium.Buffer.createIndexBuffer({
        context: context,
        typedArray: new Uint16Array([0,1,1,2,2,0]),
        usage: Cesium.BufferUsage.STATIC_DRAW,
        indexDatatype: Cesium.IndexDatatype.UNSIGNED_SHORT
    });

    var attributes = [{
        index: 0,
        enabled: true,
        vertexBuffer: vertexBuffer,
        componentsPerAttribute: 3,
        componentDatatype : Cesium.ComponentDatatype.FLOAT,
        normalize: false,
        offsetInBytes: 0,
        strideInBytes: 0
    }];
    var vs =
        "attribute vec3 position; \n" +
        "void main() { \n" +
        "    gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n" +
        "} \n";
    var fs =
        "void main() { \n" +
        "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n" +
        "} \n";
    var shaderProgram = Cesium.ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs
    });

    var vertexArray = new Cesium.VertexArray({
        context : context,
        attributes : attributes,
        indexBuffer : indexBuffer
    });

    var renderState = Cesium.RenderState.fromCache({
        cull: {
            enabled: true,
            face: Cesium.CullFace.FRONT
        },
        depthTest: {
            // enabled: true
            enabled: false
        },
        depthMask: true,
        blending: undefined
    });

    var drawCommand = new Cesium.DrawCommand({
        //owner: this,
        vertexArray : vertexArray,
        shaderProgram : shaderProgram,
        modelMatrix : Cesium.Matrix4.IDENTITY,
        renderState :  renderState,
        cull : false,
        primitiveType : Cesium.PrimitiveType.LINES,
        pass : Cesium.Pass.OPAQUE,
    });

    this.drawCommand = drawCommand;
};

Hi there,

I would recommend checking out PolylineVS.glsl to see how this is handled in CesiumJS internally. I think you’re looking for czm_viewportTransformation and czm_viewportOrthographic.

Thank you for your prompt reply and advice.

I have checked out PolylineVS.glsl.

In the code, czm_viewportOrthographic is certainly used to get gl_Position from window coordinates,
though czm_viewportTransformation is not used at all.

In case of using czm_viewportTransformation, which transform normalized device coordinates to window coordinates, so the code might be look like;

// positionNDC... vec4  normalized device coordinates (ndc) 
gl_Position = czm_viewportOrthographic * czm_viewportTransformation * positionNDC;

Now, my question is what are the steps to transform from model coordinates to normalized device coordinates, and at where View mode should be handled.

As I referred PolylineVS.glsl, it takes completely different steps, such as:

czm_translateRelativeToEye(view mode handling here) → czm_modelViewRelativeToEye → czm_eyeToWindowCoordinates

A model position consists of two EncodedCartesian3 attributes, such as position2D(High/Low) and position3D(High/Low).

Is this the way we should follow?
If yes, then how to make these two EncodedCartesian3 attributes from a model position?

Any help would be appreciated.

If yes, then how to make these two EncodedCartesian3 attributes from a model position?

In order to emulate double precision values for positions, we split position into two different attributes: positionHigh and positionLow. Internally, we use the private class EncodedCartesian3 to get these values.

Thank you for your reply.

My question is not about EncodedCartesian3 itself but about positon2D and position3D.

what a difference between positon2D and position3D ?

I believe these two are originally made from the same single model position. How to make these two?

Finally I have found a way of rendering in 2D/Columbus view for DrawCommand.

  • The key of solution is to prepare two set of vertex coordinates, one for 3D view and another for 2D view.

  • In a shader program(.vert), select the set of vertex coordinates to use depending on the current view mode.

  • when Columbus view, in shader, compute a vertex coordinates by mixing of 3Dview and 2Dview vertex coordinates.

  • In shader, a vertex coordinates is a world coordinates, and is used to compute gl_Position of clip coordinates.

These two set of vertex coordinates are originally made from a single set of vertex coordinates in (lon,lat,heght).

  • a vertex coordinates for 3D view is in a Cesium.Cartesian3 (x,y,z) coordinate system, transformation from (lon,lat,height) to 3D-view (x,y,z) is …

    • a vertex coordinates for 3D view = Cesium.Cartesian3.fromDegrees(lon, lat, height)
  • a vertex coordinates for 2D view is also (x,y,z), and spread out in Y-Z axis planar. transformation from (lon,lat,height) to 2D-view (x,y,z) is …

    • a vertex coordinates for 2D view = (height, radian(longitude)*semimajorAxis*pi, radian(latitude)*semimajorAxis*pi)
      • semimajorAxis = Cesium.Ellipsoid.WGS84._maximumRadius; // Earth radius in west-east direction
      • pi = 3.14159265359…

Following is revised code…

var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;

const basePosition = [ -77., 39.];  // Washington, DC
let sizing = 10.; // in degree
let height = 0.0;  //1000000.; // in meter

// define in 3D world coordinates
let position0_3D = Cesium.Cartesian3.fromDegrees(basePosition[0]-sizing, basePosition[1]-(sizing/2.), height);
let position1_3D = Cesium.Cartesian3.fromDegrees(basePosition[0]   , basePosition[1]+(sizing/2.), height);
let position2_3D = Cesium.Cartesian3.fromDegrees(basePosition[0]+sizing, basePosition[1]-(sizing/2.), height);
let positions_3D = [
    position0_3D.x,position0_3D.y,position0_3D.z,
    position1_3D.x,position1_3D.y,position1_3D.z,
    position2_3D.x,position2_3D.y,position2_3D.z];

// define in 2D world coordinates
//  world coordinates defined on YZ plane
//    -pi< longitude_rad <+pi   ->  -semimajorAxis*pi   < Y coordinate < +semimajorAxis*pi
//  -pi/2< latitude_rad  <+pi/2 ->  -semimajorAxis*pi/2 < Z coordinate < +semimajorAxis*pi/2
//  Remark about Z axis: NOT use Earth radius in north-south direction but use radius in west-east direction
let semimajorAxis = Cesium.Ellipsoid.WGS84._maximumRadius;  // Earth radius in west-east direction
let degree2Meter = semimajorAxis*Cesium.Math.PI/180.  // transform a lon/lat degree to a YZ coords
let position0_2D = {
    x: height,
    y: (basePosition[0]-sizing)*degree2Meter,
    z: (basePosition[1]-sizing/2.)*degree2Meter,
    };
let position1_2D = {
    x: height,
    y: (basePosition[0])*degree2Meter,
    z: (basePosition[1]+sizing/2.)*degree2Meter,
};
let position2_2D = {
    x: height,
    y: (basePosition[0]+sizing)*degree2Meter,
    z: (basePosition[1]-sizing/2.)*degree2Meter,
};
let positions_2D = [
    position0_2D.x,position0_2D.y,position0_2D.z,
    position1_2D.x,position1_2D.y,position1_2D.z,
    position2_2D.x,position2_2D.y,position2_2D.z];

scene.primitives.add(new MyPrimitive());

function MyPrimitive() {
    this.drawCommand = undefined;
}

MyPrimitive.prototype.update = function(frameState) {
    if (Cesium.defined(this.drawCommand)) {
        frameState.commandList.push(this.drawCommand);
    }
    var context = frameState.context;
    // 3D world coords
    var vertexBuffer_3D = Cesium.Buffer.createVertexBuffer({
        context: context,
        typedArray: new Float32Array(positions_3D),
        usage: Cesium.BufferUsage.STATIC_DRAW
    });
    // 2D world coords
    var vertexBuffer_2D = Cesium.Buffer.createVertexBuffer({
        context: context,
        typedArray: new Float32Array(positions_2D),
        usage: Cesium.BufferUsage.STATIC_DRAW
    });

    var indexBuffer = Cesium.Buffer.createIndexBuffer({
        context: context,
        typedArray: new Uint16Array([0,1,1,2,2,0]),
        usage: Cesium.BufferUsage.STATIC_DRAW,
        indexDatatype: Cesium.IndexDatatype.UNSIGNED_SHORT
    });

    var attributes = [
        {
            index: 0,  //3D world coords
            enabled: true,
            vertexBuffer: vertexBuffer_3D,
            componentsPerAttribute: 3,
            componentDatatype : Cesium.ComponentDatatype.FLOAT,
            normalize: false,
            offsetInBytes: 0,
            strideInBytes: 0
        }
        ,
        {
            index: 1,  //2D world coords
            enabled: true,
            vertexBuffer: vertexBuffer_2D,
            componentsPerAttribute: 3,
            componentDatatype : Cesium.ComponentDatatype.FLOAT,
            normalize: false,
            offsetInBytes: 0,
            strideInBytes: 0
        }
    ];
    var vs =
        "attribute vec3 position3D; \n" +
        "attribute vec3 position2D; \n" +
        "\n" +
        "vec3 getPosition(){\n" +
        "    if (czm_morphTime == 1.0)\n" +
        "    { // in 3V view mode\n" +
        "        return position3D;\n" +
        "    }\n" +
        "    else if (czm_morphTime == 0.0)\n" +
        "    { // in 2V view mode\n" +
        "        return position2D;\n" +
        "    }\n" +
        "    else\n" +
        "    { // Columbus view mode\n" +
        "        // p = position2D * (1-morphTime) + position3D * morphTime\n" +
        "        vec3 p = mix(position2D, position3D, czm_morphTime);  \n" +
        "        return p;\n" +
        "    }\n" +
        "}\n" +
        "\n" +
        "void main() { \n" +
        "    vec3 world_Position = getPosition(); // depends on view mode\n" +
        "    gl_Position = czm_modelViewProjection * vec4(world_Position, 1.); // world coords to clip coords\n" +
        "} \n";
    var fs =
        "void main() { \n" +
        "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n" +
        "} \n";
    var shaderProgram = Cesium.ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs
    });

    var vertexArray = new Cesium.VertexArray({
        context : context,
        attributes : attributes,
        indexBuffer : indexBuffer
    });

    var renderState = Cesium.RenderState.fromCache({
        cull: {
            enabled: true,
            face: Cesium.CullFace.FRONT
        },
        depthTest: {
            enabled: true
            // enabled: false
        },
        depthMask: true,
        blending: undefined
    });

    var drawCommand = new Cesium.DrawCommand({
        //owner: this,
        vertexArray : vertexArray,
        shaderProgram : shaderProgram,
        modelMatrix : Cesium.Matrix4.IDENTITY,
        renderState :  renderState,
        cull : false,
        primitiveType : Cesium.PrimitiveType.LINES,
        pass : Cesium.Pass.OPAQUE,
    });

    this.drawCommand = drawCommand;
};

Following is the result of views


3D view


2D view

columbusview
Columbus view

By the way,
As an alternative solution, It is possible to feed a set of vertex coordinates in (lon,lat,height) to a shader, and perform coordinate transformation to 3Dview, 2Dview coordinates inside shader code.

Thank you, Gabby_Getz.
Your suggesion was good starting point for me, It took a long way to fix though.