شکل ها را بکشید

بعد از اینکه اشکالی را برای ترسیم با OpenGL تعریف کردید، احتمالاً می خواهید آنها را بکشید. ترسیم اشکال با OpenGL ES 2.0 کمی بیشتر از آنچه تصور می‌کنید به کد نیاز دارد، زیرا API کنترل زیادی روی خط لوله رندر گرافیکی فراهم می‌کند.

این درس نحوه ترسیم اشکالی را که در درس قبلی تعریف کرده اید با استفاده از OpenGL ES 2.0 API توضیح می دهد.

مقداردهی اولیه اشکال

قبل از انجام هر طراحی، باید اشکالی را که می خواهید ترسیم کنید، مقداردهی اولیه کرده و بارگذاری کنید. مگر اینکه ساختار (مختصات اصلی) اشکالی که در برنامه خود استفاده می کنید در طول اجرا تغییر کند، باید آنها را در متد onSurfaceCreated() رندر خود برای حافظه و کارایی پردازش مقداردهی کنید.

کاتلین

class MyGLRenderer : GLSurfaceView.Renderer {
    ...
    private lateinit var mTriangle: Triangle
    private lateinit var mSquare: Square

    override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
        ...
        // initialize a triangle
        mTriangle = Triangle()
        // initialize a square
        mSquare = Square()
    }
    ...
}

جاوا

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}

یک شکل بکشید

ترسیم یک شکل تعریف شده با استفاده از OpenGL ES 2.0 به مقدار قابل توجهی کد نیاز دارد، زیرا باید جزئیات زیادی را به خط لوله رندر گرافیک ارائه دهید. به طور خاص باید موارد زیر را تعریف کنید:

  • Vertex Shader - کد گرافیکی OpenGL ES برای رندر رئوس یک شکل.
  • Fragment Shader - کد OpenGL ES برای رندر کردن صورت یک شکل با رنگ یا بافت.
  • برنامه - یک شی OpenGL ES که حاوی سایه بان هایی است که می خواهید برای ترسیم یک یا چند شکل استفاده کنید.

برای ترسیم یک شکل به حداقل یک سایه زن رأس و برای رنگ آمیزی آن شکل به یک سایه زن قطعه نیاز دارید. این سایه زن ها باید کامپایل شوند و سپس به یک برنامه OpenGL ES اضافه شوند، که سپس برای ترسیم شکل استفاده می شود. در اینجا مثالی از نحوه تعریف سایه‌زن‌های اصلی وجود دارد که می‌توانید از آن برای رسم شکل در کلاس Triangle استفاده کنید:

کاتلین

class Triangle {

    private val vertexShaderCode =
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}"

    private val fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}"

    ...
}

جاوا

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

شیدرها حاوی کد OpenGL Shading Language (GLSL) هستند که باید قبل از استفاده از آن در محیط OpenGL ES کامپایل شوند. برای کامپایل این کد، یک متد کاربردی در کلاس رندر خود ایجاد کنید:

کاتلین

fun loadShader(type: Int, shaderCode: String): Int {

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    return GLES20.glCreateShader(type).also { shader ->

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
    }
}

جاوا

public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

برای ترسیم شکل خود، باید کد سایه زن را کامپایل کنید، آنها را به یک شی برنامه OpenGL ES اضافه کنید و سپس برنامه را پیوند دهید. این کار را در سازنده شی ترسیم شده خود انجام دهید، بنابراین فقط یک بار انجام می شود.

توجه: کامپایل کردن سایه بان های OpenGL ES و پیوند دادن برنامه ها از نظر چرخه های CPU و زمان پردازش گران است، بنابراین باید از انجام این کار بیش از یک بار خودداری کنید. اگر محتوای سایه‌زن‌های خود را در زمان اجرا نمی‌دانید، باید کد خود را طوری بسازید که فقط یک بار ایجاد شود و سپس برای استفاده بعدی ذخیره شود.

کاتلین

class Triangle {
    ...

    private var mProgram: Int

    init {
        ...

        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram().also {

            // add the vertex shader to program
            GLES20.glAttachShader(it, vertexShader)

            // add the fragment shader to program
            GLES20.glAttachShader(it, fragmentShader)

            // creates OpenGL ES program executables
            GLES20.glLinkProgram(it)
        }
    }
}

جاوا

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                        vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                        fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

در این مرحله، شما آماده هستید تا تماس های واقعی را که شکل شما را ترسیم می کند، اضافه کنید. ترسیم اشکال با OpenGL ES مستلزم آن است که چندین پارامتر را مشخص کنید تا به خط لوله رندر بگویید چه چیزی را می خواهید ترسیم کنید و چگونه آن را ترسیم کنید. از آنجایی که گزینه های طراحی می توانند بر اساس شکل متفاوت باشند، ایده خوبی است که کلاس های شکل شما دارای منطق طراحی خاص خود باشند.

یک متد draw() برای رسم شکل ایجاد کنید. این کد مقادیر موقعیت و رنگ را بر روی سایه‌زن رأس شکل و سایه‌زن قطعه قرار می‌دهد و سپس تابع ترسیم را اجرا می‌کند.

کاتلین

private var positionHandle: Int = 0
private var mColorHandle: Int = 0

private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex

fun draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram)

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also {

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(it)

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                it,
                COORDS_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride,
                vertexBuffer
        )

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle ->

            // Set color for drawing the triangle
            GLES20.glUniform4fv(colorHandle, 1, color, 0)
        }

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(it)
    }
}

جاوا

private int positionHandle;
private int colorHandle;

private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

public void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(positionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(colorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(positionHandle);
}

هنگامی که همه این کدها را در جای خود دارید، رسم این شی فقط به فراخوانی draw() از درون متد onDrawFrame() رندر شما نیاز دارد:

کاتلین

override fun onDrawFrame(unused: GL10) {
    ...

    mTriangle.draw()
}

جاوا

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

هنگامی که برنامه را اجرا می کنید، باید چیزی شبیه به این باشد:

شکل 1. مثلثی که بدون نمایان شدن تصویر یا دوربین کشیده شده است.

چند مشکل با این مثال کد وجود دارد. اول از همه، قرار نیست دوستان شما را تحت تاثیر قرار دهد. ثانیاً، مثلث کمی له شده و با تغییر جهت صفحه نمایش دستگاه تغییر شکل می دهد. دلیل کج بودن شکل به این دلیل است که رئوس جسم برای نسبت های ناحیه صفحه نمایش که در آن GLSurfaceView نمایش داده می شود اصلاح نشده است. می‌توانید با استفاده از نمایش تصویر و دوربین در درس بعدی این مشکل را برطرف کنید.

در نهایت، مثلث ثابت است، که کمی خسته کننده است. در درس افزودن حرکت ، این شکل را به چرخش در می آورید و از خط لوله گرافیکی OpenGL ES استفاده جالب تری می کنید.