بعد از اینکه اشکالی را برای ترسیم با 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(); }
هنگامی که برنامه را اجرا می کنید، باید چیزی شبیه به این باشد:
چند مشکل با این مثال کد وجود دارد. اول از همه، قرار نیست دوستان شما را تحت تاثیر قرار دهد. ثانیاً، مثلث کمی له شده و با تغییر جهت صفحه نمایش دستگاه تغییر شکل می دهد. دلیل کج بودن شکل به این دلیل است که رئوس جسم برای نسبت های ناحیه صفحه نمایش که در آن GLSurfaceView
نمایش داده می شود اصلاح نشده است. میتوانید با استفاده از نمایش تصویر و دوربین در درس بعدی این مشکل را برطرف کنید.
در نهایت، مثلث ثابت است، که کمی خسته کننده است. در درس افزودن حرکت ، این شکل را به چرخش در می آورید و از خط لوله گرافیکی OpenGL ES استفاده جالب تری می کنید.