質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.36%

  • Xamarin

    314questions

    Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

  • OpenGL

    132questions

    OpenGLは、プラットフォームから独立した、デスクトップやワークステーション、モバイルサービスで使用可能な映像処理用のAPIです。

XamarinにてOpenTKで線描画がしたい

解決済

回答 1

投稿 2017/11/08 20:59

  • 評価
  • クリップ 0
  • VIEW 130

crew8573

score 2

Xamarin.FormsにてOpenTKを使用して線描画がしたいです。

C++でOpenGLを使用していた頃は、以下のような形でシンプルに線を描画できていたのですが、
C#でOpenTKを使用してopenGLの機能を使おうとするとメソッドの形式等が変更が多すぎて実現できません。

    glColor3d(1.0, 0.0, 0.0);  //「3」色で「d」ouble型

    glBegin(GL_LINES);
    glVertex2d(-0.9,-0.9);
    glVertex2d(0.9,0.6);
    glEnd();

上記のBeginやEnd, Vertex2dのようなメソッドが呼び出すことができず、
参考サイトを見たところDrawArraysというメソッドを使っておりました。

私がプロジェクトで参照しているdllはVisual Studio2017インストール時に
同時にインストールされたOpenTK-1.0.dllというものです。

開発するために参考にできるサイトはないでしょうか?
また、DrawArraysを利用した簡単な線描画(グラデーション線等)の方法を教えていただけないでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

残念ながら昔のOpenGLのように直感的に書くことは難しいかと思います。

参考サイトですが、おそらくXamarin.FormsとOpenTKの組み合わせに限定して情報を探そうとしても、ほとんど出てこなかったのではないでしょうか?
まずは何かしら描画させるところまで成功させて(ここまでが一番のハードルかもしれません)、あとは他の環境向けの情報をご自身の環境に合わせて読み替えるのがよさそうです。特に、ご質問者さんの環境とOpenGLのバージョンが一致する情報が有用かと思います。

Xamarinではないですが、OpenTKでキューブを表示 | Miyabiarts.netは参考になりそうに思いました。#region OpenTKも面白そうですが、残念ながら大部分の解説は古いOpenGLに基づいているようで、ご質問者さんの環境には応用しにくいでしょう。

ご参考になればと思いまして、OpenTK: C#でマルチプラットフォームのOpenGLコードを実装する - Qiitaで紹介されていたOpenGLViewを使ったパターンを試してみました。あいにくXamarinの使用経験がなく、Xamarinの流儀らしくない箇所もあるかもしれませんがご容赦ください。
Visual Studio Community 2017 for Macを使用し、iOS向けにビルド、iOSシミュレータ上で実行してみました。

using System;
using System.Runtime.InteropServices;
using OpenTK.Graphics.ES30;
using Xamarin.Forms;

namespace XamarinGLTest
{
    public class MainPage : ContentPage
    {
        public MainPage()
        {
            this.Title = "OpenGL Test";

            var view = new OpenGLView { HasRenderLoop = true };
            var toggle = new Switch { IsToggled = true };
            var button = new Button { Text = "Display" };

            view.HeightRequest = 300;
            view.WidthRequest = 300;

            view.OnDisplay = this.OnDisplay;

            toggle.Toggled += (s, a) => {
                view.HasRenderLoop = toggle.IsToggled;
            };
            button.Clicked += (s, a) => view.Display();

            var stack = new StackLayout
            {
                Padding = new Size(20, 20),
                Children = { view, toggle, button }
            };

            Content = stack;
        }

        private static readonly int SizeOfFloat = Marshal.SizeOf(typeof(float));

        private bool glIsInited;
        private float[] lineData;
        private int lineVAO;
        private int lineVBO;
        private int lineShader;

        private void InitGL()
        {
            // バッファクリアの際の色を指定
            GL.ClearColor(0.0f, 0.5f, 1.0f, 1.0f);

            // バーテックスシェーダーを作成
            // 内容は入力された頂点座標と頂点色をフラグメントシェーダーへ送るだけ
            var lineVertShader = GL.CreateShader(ShaderType.VertexShader);
            GL.ShaderSource(lineVertShader, 
@"#version 100

attribute vec2 inPosition;
attribute vec3 inColor;

varying lowp vec3 v2fColor;

void main()
{
    v2fColor = inColor;
    gl_Position = vec4(inPosition, 0.0, 1.0);
}
");
            GL.CompileShader(lineVertShader);

            {
                // 念のためバーテックスシェーダーがちゃんと作成できたかチェック
                GL.GetShader(lineVertShader, ShaderParameter.CompileStatus, out var result);
                if (result == (int)All.False)
                {
                    Console.WriteLine(GL.GetShaderInfoLog(lineVertShader));
                    throw new Exception("Error! Can't compile vertex shader.");
                }
            }

            // フラグメントシェーダーを作成
            // 内容は送られてきた色(自動的に頂点間で線形補間されている)をそのフラグメントの色とするだけ
            var lineFragShader = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(lineFragShader,
@"#version 100

varying lowp vec3 v2fColor;

void main()
{
    gl_FragColor = vec4(v2fColor, 1.0);
}
");
            GL.CompileShader(lineFragShader);

            {
                // 念のためフラグメントシェーダーがちゃんと作成できたかチェック
                GL.GetShader(lineFragShader, ShaderParameter.CompileStatus, out var result);
                if (result == (int)All.False)
                {
                    Console.WriteLine(GL.GetShaderInfoLog(lineFragShader));
                    throw new Exception("Error! Can't compile fragment shader.");
                }
            }

            // バーテックスシェーダー、フラグメントシェーダーをまとめたシェーダープログラムを作る
            this.lineShader = GL.CreateProgram();
            GL.AttachShader(this.lineShader, lineVertShader);
            GL.AttachShader(this.lineShader, lineFragShader);
            GL.LinkProgram(this.lineShader);
            GL.DeleteShader(lineVertShader);
            GL.DeleteShader(lineFragShader);

            {
                // 念のためシェーダープログラムがちゃんと作成できたかチェック
                GL.ValidateProgram(this.lineShader);
                GL.GetProgram(this.lineShader, ProgramParameter.ValidateStatus, out var succeeded);
                if (succeeded == (int)All.False)
                {
                    Console.WriteLine("\t{0}", GL.GetProgramInfoLog(this.lineShader));
                    throw new Exception(string.Format("Program {0} is invalid.", this.lineShader));
                }
            }

            // 線のデータ
            // とりあえず位置として2要素、色として3要素のデータを128頂点分格納できるようにした
            this.lineData = new float[128 * (2 + 3)];

            // 頂点配列オブジェクトを作成
            // このオブジェクトがこのあと設定する頂点属性云々といった情報を保管してくれる
            GL.GenVertexArrays(1, out this.lineVAO);
            GL.BindVertexArray(this.lineVAO);

            // 頂点バッファオブジェクトを作成
            // この中にlineDataの中身を適宜転送してやり、描画時OpenGLはこのバッファオブジェクトから位置やら色やらの情報を取っていく
            GL.GenBuffers(1, out this.lineVBO);
            // グラフィックスメモリ上に領域を確保
            // lineDataの内容を転送しているが、この時点では中身は空
            GL.BindBuffer(BufferTarget.ArrayBuffer, this.lineVBO);
            GL.BufferData<float>(BufferTarget.ArrayBuffer, new IntPtr(SizeOfFloat * this.lineData.Length), this.lineData, BufferUsage.DynamicDraw);

            {
                // 位置座標属性
                // 要素数2個のfloat、次の頂点までのストライドはsizeOfFloat * (2 + 3)バイト、オフセットは頂点属性の頭から0バイト
                var attribLoc = GL.GetAttribLocation(this.lineShader, "inPosition");
                GL.EnableVertexAttribArray(attribLoc);
                GL.VertexAttribPointer(attribLoc, 2, VertexAttribPointerType.Float, false, SizeOfFloat * (2 + 3), 0);
            }

            {
                // 色属性
                // 要素数3個のfloat、次の頂点までのストライドは同じくsizeOfFloat * (2 + 3)バイト、オフセットは頂点属性の頭からsizeOfFloat * 2バイト
                var attribLoc = GL.GetAttribLocation(this.lineShader, "inColor");
                GL.EnableVertexAttribArray(attribLoc);
                GL.VertexAttribPointer(attribLoc, 3, VertexAttribPointerType.Float, false, SizeOfFloat * (2 + 3), SizeOfFloat * 2);
            }

            GL.BindVertexArray(0);

            this.glIsInited = true;
        }

        private void OnDisplay(Rectangle r)
        {
            if (!this.glIsInited)
            {
                this.InitGL();
            }

            GL.Clear(ClearBufferMask.ColorBufferBit);

            // どのシェーダープログラムを使うか、どの頂点配列を使うかといったことをこれらメソッドで指示する
            // 今回のケースでは必ずしも毎フレーム行う必要はないですが、とりあえず書いておきました
            GL.UseProgram(this.lineShader);
            GL.BindVertexArray(this.lineVAO);

            // lineDataの内容を更新する
            // 形の変わらない3Dモデルなどでは毎フレームこんなことをする必要はないのですが...
            var vertexCount = 0;
            {
                var a = this.lineData;

                {
                    var o = vertexCount * (2 + 3);
                    // 頂点0の位置
                    a[o + 0] = -0.9f;
                    a[o + 1] = -0.9f;
                    // 頂点0の色
                    a[o + 2] = 1.0f;
                    a[o + 3] = 0.0f;
                    a[o + 4] = 0.0f;
                    vertexCount++;
                }

                {
                    var o = vertexCount * (2 + 3);
                    // 頂点1の位置
                    a[o + 0] = 0.9f;
                    a[o + 1] = 0.6f;
                    // 頂点1の色
                    a[o + 2] = 0.0f;
                    a[o + 3] = 0.0f;
                    a[o + 4] = 1.0f;
                    vertexCount++;
                }

                {
                    var o = vertexCount * (2 + 3);
                    // 頂点2の位置
                    a[o + 0] = 0.2f;
                    a[o + 1] = -0.5f;
                    // 頂点2の色
                    a[o + 2] = 0.0f;
                    a[o + 3] = 1.0f;
                    a[o + 4] = 0.0f;
                    vertexCount++;
                }

                {
                    var o = vertexCount * (2 + 3);
                    // 頂点3の位置
                    a[o + 0] = -0.2f;
                    a[o + 1] = 0.4f;
                    // 頂点3の色
                    a[o + 2] = 1.0f;
                    a[o + 3] = 1.0f;
                    a[o + 4] = 1.0f;
                    vertexCount++;
                }
            }

            // グラフィックスメモリ上の頂点バッファオブジェクトに、更新したデータを転送する
            GL.BindBuffer(BufferTarget.ArrayBuffer, this.lineVBO);
            GL.BufferSubData<float>(BufferTarget.ArrayBuffer, IntPtr.Zero, new IntPtr(SizeOfFloat * (2 + 3) * vertexCount), this.lineData);

            // 現在バインドされている頂点バッファから、頂点0番からvertexCount個分の頂点データを取ってきて描画する
            // BeginModeを変えると点や三角形として描くこともできる
            GL.DrawArrays(BeginMode.LineStrip, 0, vertexCount);
        }
    }
}

やたら長くなって申し訳ないですが、肝になるのはInitGLOnDisplayでやっている部分です。
大まかな流れは、まず準備として

  • 描画用のシェーダーを作る
  • 描画したい物体の頂点データを詰めた配列を作る

を行っておき、あとは毎フレーム

  • 頂点データを書き換えたい場合は適宜書き換え、新しいデータを転送する
  • 描画コマンド(GL.DrawArraysなど)を使って物体を描く

といった感じです。上記コードはあくまで一例ですので、参考サイトによっては頂点データの作り方やその他細部に違いがあるでしょうが、大体のところは変わらないかと思います。

ちなみに実行時の様子はこうなりました。

スクリーンショット

投稿 2017/11/12 16:59

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/19 18:25

    返信が遅くなってしまいすみません。
    実装したら動かすことができました。
    以前のように直感的なプログラムで書けると楽なんですけどね。
    教えていただいた使い方で実装を進めてみます!

    ありがとうございました。

    キャンセル

  • 2017/11/19 18:58

    ご参考になりましたようで幸いです。

    ある程度慣れてきたら、ややこしい部分を隠蔽した独自メソッドを作ってみてもいいかもしれませんね。
    たとえば

    ・glColorもどき...引数の色をインスタンス変数に保管する
    ・glBeginもどき...引数のBeginModeをインスタンス変数に保管、およびvertexCountを0にリセットする
    ・glVertexもどき...引数の座標とインスタンス変数に保管しておいた色を使って、lineDataに1頂点分のデータを書き込んでvertexCountを1増やす
    ・glEndもどき...UseProgram、BindVertexArray、BindBufferを行い、lineDataをlineVBOに転送したのち、インスタンス変数に保管しておいたBeginModeとvertexCountを使ってDrawArraysを行う

    といった感じで作れそうです。

    あるいは逆にもっとオブジェクト指向っぽくなるように、シェーダークラス、テクスチャクラス、形状データクラス...といったものを作ってみるのも面白いと思います。Disposeパターンと相性がよさそうですね。コンストラクタでGen系メソッドを使って必要なオブジェクトを生成し、DisposeでDelete系メソッドを使って作ったオブジェクトを削除する...といったところでしょうか。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.36%

関連した質問

同じタグがついた質問を見る

  • Xamarin

    314questions

    Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

  • OpenGL

    132questions

    OpenGLは、プラットフォームから独立した、デスクトップやワークステーション、モバイルサービスで使用可能な映像処理用のAPIです。