원문 : http://www.raywenderlich.com/3664/opengl-tutorial-for-ios-opengl-es-2-0
원문을 읽으면서 번역한다. 이거 시간 오래걸리네.
OpenGL ES is the lowest-level API that you use to program 2D and 3D graphics on the iPhone.
OpenGL ES 는 아이폰에서 2D 와 3D 프로그램을 만들수 있는 저수준 API 이다.
If you’ve used other framework such as Cocos2D, Sparrow, Corona, or Unity, these are all built on top of OpenGL!
Cocos2D, Sparrow, Corona, Unity 같은 다른 프레임워크들도 모두 OpenGL을 기반으로 한다.
One of the reasons why programmers like to use the above frameworks rather than using OpenGL directly is because OpenGL is notoriously difficult to learn.
프로그래머가 OpenGL을 직접 사용하기보다는, 위의 프레임워크들을 사용하는걸 좋아하는 이유는
OpenGL 이 배우기가 악명높게 어렵기때문이다.
And that’s what this OpenGL tutorial is for – to make the learning curve a little less steep for beginner OpenGL developers!
이 OpenGL 튜토리얼의 목적은 초보자의 학습곡선을 덜 가파르게 만들어주는 것이다.
In this series, you’ll get hands-on experience with OpenGL ES 2.0 and will create a simple “Hello, World” app that displays some simple geometry.
이 시리즈에서 너는 OpenGL ES 2.0 의 실전경험을 얻을수 있다.
그리고, 간단한 3D 객체를 보여주는 앱을 만들수 있다.
In the process, you’ll learn the following:
이 과정에서 너는 아래의 것들을 배울수 있다.
How to get a basic OpenGL app working from scratch
사전지식없이 어떻게 Open GL 앱이 작동하는지의 기본을 얻을수 있다.
How to compile and run vertex & fragment shaders
어떻게 vertex shader 와 fragment shader 가 컴파일 되는지 알수 있다.
How to render a simple square to the screen with vertex buffer objects
vertex buffer object 들에서 간단한 사각형이 어떻게 그려지는지 알수 있다.
How to apply projection and model-view transforms
projection 과 모델뷰의 변환을 어떻게 적용하는지 알수 있다.
How to render a 3D object with depth testing
깊이테스트를 하면서 어떻게 삼차원 객체를 그릴수 있는지 알수 있다.
Caveat: I am not an Open GL expert! I am learning this myself, and am writing tutorials as I go.
통보 : 나는 Open GL 전문가가 아니다. 나는 스스로 독학하면서, 이 튜토리얼을 쓰고 있다.
If I make any boneheaded mistakes, feel free to chime in with corrections or insights! :]
만약 내가 멍청한 실수를 했다면, 편하게 알려달라
* Open GL ES 1.0 vs OpenGL ES 2.0
First things first – you should know that there are two different versions of OpenGL ES (1.0 and 2.0), and they are very different.
먼저 Open GL ES 에는 2가지 버젼이 있고 (1.0 과 2.0) 그 둘은 매우 다르다른걸 알아야 한다.
OpenGL ES 1.0 uses a fixed pipeline, which is a fancy way of saying you use built-in functions to set lights, vertexes, colors, cameras, and more.
1.0 은 고정파이프라인을 사용하고, 이것은 빛, 꼭지점, 색, 카메라등 설정할때 내장된 함수를 사용할수 있는 멋진 방법이다.
OpenGL ES 2.0 uses a programmable pipeline, which is a fancy way of saying all those built-in functions go away, and you have to write everything yourself.
2.0 은 프로그래머블 파이프라인을 사용하고, 내장된 함수를 버리고, 너가 직접 모든것을 작성할수 있는 멋진 방법이다.
“OMG!” you may think, “well why would I ever want to use OpenGL ES 2.0 then, if it’s just extra work?!”
Although it does add some extra work, with OpenGL ES 2.0 you make some really cool effects
that wouldn’t be possible in OpenGL ES 1.0, such as this toon shader
"오마이갓, 추가적인 작업이 있다면, 왜 내가 2.0 을 쓰길 원했을까"" 라고 너는 아마 생각할수도 있다.
그렇지만 2.0 에서 추가적인 작업이 있다 하더라도 toon shader 같은 진짜 멋진 효과를 만들수 있다.
(1.0 에서는 불가능하다)
* Getting Started
Although Xcode comes with an OpenGL ES project template, I think that’s confusing for beginners because you have to go through a lot of code you didn’t write yourself and try to understand how it works.
xcode OpenGL ES 프로젝트 템플릿을 제공해준다고 해도,
직접 작성하지도 않은 많은 양의 코드를 훍어봐야 하기 때문에, 어떻게 작동하는지 이해하기가
초보자에게 혼란을 줄수도 있을것이다.
I think it’s easier if you write all the code from scratch, so you can understand how everything fits together – so that’s what we’re going to do here!
너가 모든 코드를 처음부터 작성한다면, 모든부분이 어우러져서 이해하기 쉬울거라고 생각한다.
Start up Xcode, and go to File\New\New Project.
Select iOS\Application\Window-based Application, and click Next.
Name your project HelloOpenGL, click Next, choose a folder to save it in, and click Create.
Compile and run the app, and you should see a blank white screen:
xcode를 시작하고, Window-based 로 새로운 프로젝트를 만든다.
이름을 HelloOpenGl 이라고 정한다.
컴파일하고 실행하면 하얀색 빈 화면을 볼수 있다.
(이석우 추가 : 이글을 번역할시점에 xcode는 6.1 이다.
그래서 window-base 는 없고, single view application 이 적당할거 같다)
The Window-based Application template is about as “from scratch” as you can get with project templates in Xcode.
All it does is create a UIWindow and present it to the screen – no views, view controllers, or anything!
xcode의 프로젝트 템플릿에서 나오는 window-based application 템플릿은 기초와 같다
UIWindow 하나만 만들어주고, 뷰도 없고, 뷰컨트롤러도 없고, 아무것도 없다.
Let’s add a new view that we’ll use to contain the OpenGL content.
Go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next.
Enter UIView for Subclass of, click Next, name the new class OpenGLView.m, and click Save.
OpenGL 을 담을 뷰를 하나 넣어보자
Cocoa Touch Class 로 파일을 추가하자
이름은 OpenGLView 하고, Subclass of 항목은 UIView 로 하자
Next, you’ll add a bunch of code to OpenGLView.m inside the @implementation to just color the screen green for now.
다음. OpenGLView.m 파일에 코드를 좀 넣자
@implementation 안에, 단지 화면색을 초록색으로 하도록
1) Add required frameworks
The first step is to add the two frameworks you need to use OpenGL – OpenGLES.frameworks and QuartzCore.framework.
To add these frameworks in Xcode 4, click on your HelloOpenGL project in the Groups & Files tree, and select the HelloOpenGL target.
Expand the Link Binary with Libraries section, click the + button, and select OpenGLES.framework.
Repeat for QuartzCore.framework as well.
OpenGLES 프레임쿼크를 xcode 에 추가하라는 내용인데
xcode6.1 에서는 추가 안해도 된다
2) Modify OpenGLView.h
Modify OpenGLView.h to look like the following:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
@interface OpenGLView : UIView {
CAEAGLLayer* _eaglLayer;
EAGLContext* _context;
GLuint _colorRenderBuffer;
}
@end
This imports the headers you need for OpenGL,
and creates the instance variables that the methods you wrote earlier were using.
OpenGLView.h 파일을 다음과 같이 고친다.
(이석우 추가 : incldue 대신 import 해도 됨)
3) Set layer class to CAEAGLLayer
+ (Class)layerClass {
return [CAEAGLLayer class];
}
To set up a view to display OpenGL content,
you need to set it’s default layer to a special kind of layer called a CAEAGLLayer.
The way you set the default layer is to simply overwrite the layerClass method, like you just did above.
OpenGL 을 화면에 표시하기 위해서는
뷰의 기본레이어를 CAEAGLLayer 라고 불리는 특별한 종류의 레이어로 설정해야 한다
위의 코드처럼, layerClass 메소드를 오버라이드 해서 기본레이어를 덮어쓰면 된다.
4) Set layer to opaque
- (void)setupLayer {
_eaglLayer = (CAEAGLLayer*) self.layer;
_eaglLayer.opaque = YES;
}
By default, CALayers are set to non-opaque (i.e. transparent).
However, this is bad for performance reasons (especially with OpenGL), so it’s best to set this as opaque when possible.
기본적으로 CALayers는 투명하다.
OpenGL 에서는 성능상의 이유로, 불투명하게 하는게 좋다
5) Create OpenGL context
- (void)setupContext {
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
_context = [[EAGLContext alloc] initWithAPI:api];
if (!_context) {
NSLog(@"Failed to initialize OpenGLES 2.0 context");
exit(1);
}
if (![EAGLContext setCurrentContext:_context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
}
To do anything with OpenGL, you need to create an EAGLContext, and set the current context to the newly created context.
An EAGLContext manages all of the information iOS needs to draw with OpenGL.
It’s similar to how you need a Core Graphics context to do anything with Core Graphics.
When you create a context, you specify what version of the API you want to use.
Here, you specify that you want to use OpenGL ES 2.0.
If it is not available (such as if the program was run on an iPhone 3G), the app would terminate.
OpenGL 로 뭔가를 하기 위해서는, EAGLContext 를 만들어야 하고
현재context 를 새로 만들어진 context 로 설정해야 한다
EAGLContext 는 iOS 에서 OpenGL 를 그릴때 필요한 모든 정보를 관리한다.
이것은 Core 그래픽으로 무엇을 할때 Core Graphics context 가 왜 필요한지와 비슷하다.
context 를 만들때, 너가 사용할 API 버젼을 기술해야 한다
여기서 우리는 2.0 으로 기술했다.
만약 2.0이 사용할수 없다면, 앱을 종료시켜야 한다.
6) Create a render buffer
- (void)setupRenderBuffer {
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
The next step to use OpenGL is to create a render buffer,
which is an OpenGL object that stores the rendered image to present to the screen.
Sometimes you’ll see a render buffer also referred to as a color buffer,
because in essence it’s storing colors to display!
There are three steps to create a render buffer:
1. Call glGenRenderbuffers to create a new render buffer object.
This returns a unique integer for the the render buffer (we store it here in _colorRenderBuffer).
Sometimes you’ll see this unique integer referred to as an “OpenGL name.”
2. Call glBindRenderbuffer to tell OpenGL “whenever I refer to GL_RENDERBUFFER, I really mean _colorRenderBuffer.”
3. Finally, allocate some storage for the render buffer.
The EAGLContext you created earlier has a method you can use for this called renderbufferStorage.
다음 작업은 render buffer 를 만드는것이다.
render buffer 는 화면에 표시할 렌더링된 이미지를 저장하는 OpenGL 객체이다.
때로 render buffer 는 color buffer 라고 언급되는데, 화면에 표시할 색을 저장하는 필수적인 것이기 때문이다.
render buffer 를 만드는 3단계는 다음과 같다
1. glGenRenderbuffers 함수를 호출하여 새로운 render buffer 객체를 만든다.
이 함수는 render buffer 를 위한 유일한 정수를 리턴한다. (우리는 _colorRenderBuffer 변수에 저장했다)
가끔 이 유일한 정수를 OpenGL name 이라고 언급하는걸 볼수 있다.
2. glBindRenderbuffer 함수를 호출하여 GL_RENDERBUFFER 를 참조할때는, _colorRenderBuffer를 의미한다고 OpenGL 에게 알려준다
3. 마지막으로 render buffer 를 위한 저장공간을 확보하기 위해서,
미리 만들어놨던 EAGLContext의 renderbufferStorage 메소드를 사용한다
7) Create a frame buffer
- (void)setupFrameBuffer {
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, _colorRenderBuffer);
}
A frame buffer is an OpenGL object that contains a render buffer,
and some other buffers you’ll learn about later such as a depth buffer, stencil buffer, and accumulation buffer.
The first two steps for creating a frame buffer is very similar to creating a render buffer
– it uses the glGen and glBind like you’ve seen before, just ending with “Framebuffer/s” instead of “Renderbuffer/s”.
The last function call (glFramebufferRenderbuffer) is new however. It lets you attach the render buffer you created earlier to the frame buffer’s GL_COLOR_ATTACHMENT0 slot.
frame buffer 는 OpenGL 객체로써, render buffer 와 다른 buffer 들을 가지고 있다.
(depth buffer, stencil buffer, accumulation buffer 같은)
frame buffer 를 생성하는 처음 2단계는 render buffer 를 만들때와 비슷하다.
glFramebufferRenderbuffer 함수는 미리 만들어 놓은 render buffer 를 frame buffer의 GL_COLOR_ATTACHMENT0 슬롯에 넣는다.
8) Clear the screen
- (void)render {
glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
We’re trying to get something displaying on the screen as quickly as possible,
so before dealing with vertexes, shaders, and the like, let’s just clear the entire screen to a particular color!
Let’s set it to the main color of this website, which is RGB 0, 104, 55.
Notice you have to divide the color values by 255 (the max color value), because the color range for each component is from 0 to 1.
To accomplish this we have to take three steps here:
Call glClearColor to specify the RGB and alpha (transparency) values to use when clearing the screen.
Call glClear to actually perform the clearing.
Remember that there can be different types of buffers, such as the render/color buffer we’re displaying,
and others we’re not using yet such as depth or stencil buffers.
Here we use the GL_COLOR_BUFFER_BIT to specify what exactly to clear – in this case, the current render/color buffer.
Call a method on the OpenGL context to present the render/color buffer to the UIView’s layer!
3차원 물체를 보여주기 전에, 가능한 빨리 화면에 뭔가를 보여주기 위해서,
그냥 화면을 특정한 색으로 가득채우도록 한다.
화면을 이 웹사이트의 메인 색으로 채우자 (rgb 0, 104, 55)
색의 값을 255(색의 최대값으로 나눈것에 유의하자. opengl 에서의 색값 범위는 0~1 이다.
이것을 하기 위한 3단계
glClearColor 함수를 호출해서, 화면을 지울때 사용할 RGBA 값을 설정한다.
glClear 함수를 호출해서, 실제로 지우는 작업을 수행한다.
다른 타입의 buffer 도 지울수 있다는걸 명심해라.
소스에서 보여준 color buffer를 지울수도 있고, 우리가 아직 사용하지 않은 depth buffer, stencil buffer 도 지울수 있다.
여기서는 GL_COLOR_BUFFER_BIT 로 설정해서 color buffer 를 지운다고 명시했다.
opengl context 의 presentRenderbuffer 메소드를 호출해서, UIView 의 layer 에 color buffer의 내용을 보여준다.
9) Wrapup code in OpenGLView.m
// Replace initWithFrame with this
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupLayer];
[self setupContext];
[self setupRenderBuffer];
[self setupFrameBuffer];
[self render];
}
return self;
}
// Replace dealloc method with this
- (void)dealloc
{
[_context release];
_context = nil;
[super dealloc];
}
* 이석우추가 : ViewController.viewDidLoad 에 아래의 내용을 추가하자
- (void)viewDidLoad
{
[super viewDidLoad];
self.view = nil;
float h = [UIScreen mainScreen].applicationFrame.size.height;
float w = [UIScreen mainScreen].applicationFrame.size.width;
OpenGLView* openGLView = [[OpenGLView alloc] initWithFrame:CGRectMake(0, 0, w, h)];
self.view = openGLView;
[openGLView release];
}
And that’s it! Compile and run your project, and you should see a green screen drawn by OpenGL ES 2.0!
끝. 컴파일 하고 실행시키면 OpenGL ES 2.0 이 그린 초록색 화면을 볼수 있다.
* Adding Vertex and Fragment Shaders
In OpenGL ES 2.0, to render any geometry to the scene, you have to create two tiny little programs called shaders.
Shaders are written in a C-like language called GLSL.
Don’t worry too much about studying up on the reference at this point – you can get just by looking at the examples in this OpenGL tutorial for now.
2.0에서 화면에 3차원물체를 그리면, shader 라고 불리는 작은 프로그램을 작성해야 한다.
shader는 GLSL 이라고 하는 c와 비슷한 언어로 작성된다.
너무 걱정마라.. 어쩌구.
There are two types of shaders:
shader는 2종류가 있다.
Vertex shaders are programs that get called once per vertex in your scene.
So if you are rendering a simple scene with a single square, with one vertex at each corner, this would be called four times.
Its job is to perform some calculations such as lighting, geometry transforms, etc.,
figure out the final position of the vertex, and also pass on some data to the fragment shader.
Vertex shader 는 하나의 꼭짓점마다 한번씩 호출되는 프로그램이다.
너가 하나의 사각형을 그린다면, 이것은 4번 호출된다 (각 모서리의 꼭짓점)
vertex shader 는 빛, 3차원변형등과 같은 계산을 수행한다.
꼭짓점의 최종위치를 알아내고, fragment shader에 데이타를 전달한다.
Fragment shaders are programs that get called once per pixel (sort of) in your scene.
So if you’re rendering that same simple scene with a single square, it will be called once for each pixel that the square covers.
Fragment shaders can also perform lighting calculations, etc, but their most important job is to set the final color for the pixel.
Fragment shader 는 하나의 픽셀마다 한번씩 호출되는 프로그램이다.
너가 하나의 사각형을 그린다면, 이것은 사각형안에 위치하고있는 픽셀마다 한번씩 호출된다.
fragment shader 역시 빛등을 계산할수 있지만, 가장 중요한 역활은 픽셀의 최종 색깔을 설정하는 것이다.
Like I said, the easiest way to understand these is to look at some examples.
Let’s keep things nice and simple and write the simplest possible vertex and fragment shaders we can!
내가 말했듯이, 이것들을 이해하는 가장좋은 방법은 샘을을 보는것이다.
가능한 가장 간단한 vertex, fragment shader를 작성해보자.
In Xcode, go to File\New\New File…, choose iOS\Other\Empty, and click Next. Name the new file SimpleVertex.glsl, and click Save.
Open up SimpleVertex.glsl and add the following code:
xcode 에서 빈파일을 하나 만들고, 다음의 코드를 넣어라.
(이석우 추가 : 파일의 확장자는 아무거나 해도 된다)
attribute vec4 Position; // 1
attribute vec4 SourceColor; // 2
varying vec4 DestinationColor; // 3
void main(void) { // 4
DestinationColor = SourceColor; // 5
gl_Position = Position; // 6
}
Everything is new here, so let’s go over it line by line!
모든것이 처음보는것이다. 한줄씩 가보자
1. The attribute keyword declares that this shader is going to be passed in an input variable called Position.
Later on, you’ll write some code to pass some data into this input variable.
It will be used to indicate the position of the Vertex.
Note that the type of the variable is a vec4, which means a vector with 4 components.
attribute 키워드는 Position 이라는 입력변수를 선언한다.
나중에 너는 이 입력변수에 데이타를 전달하는 코드를 작성하게 될거다
이것은 꼭짓점의 위치를 가르키는 용도로 사용된다.
변수의 타입이 vec4 라는것에 주의해라. 4개의 요소를 가지는 벡터라는 뜻이다.
2. This declares a second input variable, for the color of the vertex.
이것은 2번째 입력변수를 선언한다. 꼭지점의 색을 위하여
3. This declares another variable,
but it doesn’t have the attribute keyword, so is an output variable that will be passed to the fragment shader.
It also has the varying keyword, which is a fancy way of saying
“I’m going to tell you the value for a particular vertex,
but when you need to figure out the value for a given pixel,
figure it out by smoothing out the values between nearby vertexes.”
attribute 키워드가 없는 다른변수를 선언했다. 이것은 fragment shader 에 전달할 출력변수이다.
varying 키워드는 다음과 같은뜻이다.
"나는 특정 꼭짓점에 대한 값을 알려줄거야. 하지만 너가 주어진 픽셀의 값이 필요할때는 인접한 꼭짓점들의 값을
부드럽게 처리하여 알아내야 해""
(이석우 추가 : 모든 꼭짓점의 색을 fragment shader 에 전달한다
fragment shader 에서 이 값을 사용할때는 인접한 꼭짓점의 색들과 보간해서 사용된다)
Huh? That last bit sounds confusing, but is actually pretty easy to understand if you see a picture:
어? 마지막 말은 혼란스럽지만, 아래의 그림을 본다면 쉽게 이해될거다.
So basically, you can specify a different color for each vertex,
and it will make all the values in-between a neat gradient! You’ll see an example of that for yourself soon.
기본적으로 각 꼭짓점마다 다른 색을 지정할수 있다.
꼭짓점 사이의 픽셀들은 부드럽게 색이 바뀐다. 곧 보게 될거다.
4. Every shader begins with a main – just like C!
모든 shader 는 main 으로 시작된다. c 언어 처럼
5. Sets the destination color for this vertex equal to the source color, and lets OpenGL interpolate the values as explained above.
이 꼭짓점의 최종색을 입력된 색과 동일하게 설정한다. OpenGL 는 위에서 설명한대로 값들을 보간한다.
6. There’s a built in output variable you have to set in the vertex shader called gl_Position equal to the final position of the vertex.
Here was just set it to the original position without changing it at all.
vertex shader 에는 내장된 출력변수가 있다 (gl_Position) 여기에 꼭짓점의 최종 위치를 설정해라.
여기서는 원래 위치를 바꾸지 않고, gl_Position 에 설정하였다.
OK, that’s it for our simple vertex shader! Let’s add a simple fragment shader next.
OK, 간단한 vertex shader 가 끝났다. 다음으로 간단한 fragment shader 를 추가해 보자.
Go to File\New\New File…, choose iOS\Other\Empty, and click Next. Name the new file SimpleFragment.glsl, and click Save.
Open up SimpleFragment.glsl and add the following code:
xcode 에서 파일을 추가하고, 다음의 코드를 넣어라
varying lowp vec4 DestinationColor; // 1
void main(void) { // 2
gl_FragColor = DestinationColor; // 3
}
This is pretty short and sweet but let’s explain it line by line anyways:
되게 간단하지만 설명하겠다.
1. This is the input variable from the vertex shader.
It’s the exact same definition as the vetex shader had, except it has an additional keyword: lowp.
Turns out when you specify variables in a fragment shader, you need to give it a precision.
A good rule of thumb is try to use the lowest precision you can get away with, for a performance bonus.
We set it to the lowest precision here, but there’s also medp and highp if you need it.
이것은 vertex shader 에서 넘어온 입력변수다.
vertex shader 에서의 선언과 동일해야만 한다.
근데 lowp 라는 키워드가 추가 되었다.
너가 fragment shader 에 변수를 기술할때, 저밀도를 지정해야 한다.
경험에서는 좋은 방법은 가장 낮은 정밀도를 지정하면 성능에 좋다.
우리는 여기서 가장 낮은 정미도를 셋팅했지만, medp, highp 도 쓸수 있다.
2. Just like in a vertex shader, a fragment shader also begins with main.
vertex shader 처럼, fragment shader 도 main 으로 시자한다.
3. Just like you need to set gl_Position in the vertex shader, you have to set gl_FragColor in the fragment shader.
This simply sets it to the destination color passed in (interpolated by OpenGL).
vertex shader 에서 gl_Position 을 설정해야 했던 것처럼,
fragment shader 에서는 gl_FragColor 를 설정해야 한다.
여기서는 간단히 넘겨받은 색으로 설정하였다.
Not so bad, eh? Now let’s add some code to get start using these in our app.
나쁘지 않지? 이제 이것들을 사용할수 있게 앱에 코드를 넣어보자
* Compiling Vertex and Fragment Shaders
We’ve added the two shaders to our Xcode project as glsl files,
but guess what – Xcode doesn’t do anything with them except copy them into our application bundle.
It’s the job our our app to compile and run these shaders – at runtime!
You might be surprised by this (it’s kinda weird to have an app compiling code on the fly!),
but it’s set up this way so that the shader code isn’t dependent on any particular graphics chip, etc.
So let’s write a method we can use to compile these shaders. Open up OpenGLView.m and add the following method above initWithFrame:
우리는 xcode에 glsl 파일 2개를 넣었다.
하지만 xcode 는 그것들을 가지고 아무것도 하지 않는다.
우리는 우리의 앱이 실행할때 shader 를 컴파일하고 실행하도록 해야한다.
앱이 실행중일때 코드를 컴파일 한다는것이 이상하고 놀라울수 있지만
shader 코드는 어떤 특정한 그래픽칩에 종속되지 않기 때문에 이렇게 되어야 하는것이다.
이 shader들을 컴파일 하고 사용할수 있도록 메소드들 작성해 보자.
OpenGLView.m 을 열고 아래의 코드를 initWithFrame 위에 넣어라
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
// 1
NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName
ofType:@"glsl"];
NSError* error;
NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath
encoding:NSUTF8StringEncoding error:&error];
if (!shaderString) {
NSLog(@"Error loading shader: %@", error.localizedDescription);
exit(1);
}
// 2
GLuint shaderHandle = glCreateShader(shaderType);
// 3
const char * shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = [shaderString length];
glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
// 4
glCompileShader(shaderHandle);
// 5
GLint compileSuccess;
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
if (compileSuccess == GL_FALSE) {
GLchar messages[256];
glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
exit(1);
}
return shaderHandle;
}
OK, let’s go over how this works:
OK, 이게 어떻게 작동하는지 보자
1. Gets an NSString with the contents of the file.
This is regular old UIKit programming, many of you should be used to this kind of stuff already.
중요한 내용 아니라서 생략.
2. Calls glCreateShader to create a OpenGL object to represent the shader.
When you call this function you need to pass in a shaderType to indicate whether it’s a fragment or vertex shader.
We take ethis as a parameter to this method.
glCreateShader 함수를 호출해서 shader 객체를 만든다.
이 함수를 호출할때, shader type 이 필요하다. vertex shader 인지 fragment shader 인지
3. Calls glShaderSource to give OpenGL the source code for this shader.
We do some conversion here to convert the source code from an NSString to a C-string.
glShaderSource 함수를 호출해서 shader에 사용할 소스코드를 OpenGL 에게 준다.
4. Finally, calls glCompileShader to compile the shader at runtime!
마지막으로, glCompileShader 함수를 호출해서 실행중에 shader 를 컴파일한다.
5. This can fail – and it will in practice if your GLSL code has errors in it.
When it does fail, it’s useful to get some output messages in terms of what went wrong.
This code uses glGetShaderiv and glGetShaderInfoLog to output any error messages to the screen (and quit so you can fix the bug!)
shader 소스에 에러가 있을경우, 이것은 실패할수 있다.
실패했을때, 무엇이 잘못됬는지 메세지를 받는것은 유용하다
glGetShaderiv, glGetShaderInfoLog 함수를 이용해서 에러메세지를 출력하고, 고칠수 있다.
You can use this method to compile the vertex and fragmetn shaders,
but there’s a few more steps –
linking them together, telling OpenGL to actually use the program,
and getting some pointers to the attribute slots where you’ll be passing in the input values to the shaders.
이 메소드(compileShader)를 사용해서 vertext shader 와 fragment shader 를 컴파일 할수 있다.
그러나 몇가지 필요한 작업이 더 있다.
그것들을 묶는것, OpenGL 에서 실제적으로 program 을 사용할수 있도록 하는것,
shader에 입력값을 전달할수 있게 attribute slot 의 포인터를 얻는것.
Add a new method to do this right below compileShader:
compileShader 바로 밑에 새로운 메소드를 추가한다.
- (void)compileShaders {
// 1
GLuint vertexShader = [self compileShader:@"SimpleVertex"
withType:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShader:@"SimpleFragment"
withType:GL_FRAGMENT_SHADER];
// 2
GLuint programHandle = glCreateProgram();
glAttachShader(programHandle, vertexShader);
glAttachShader(programHandle, fragmentShader);
glLinkProgram(programHandle);
// 3
GLint linkSuccess;
glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
exit(1);
}
// 4
glUseProgram(programHandle);
// 5
_positionSlot = glGetAttribLocation(programHandle, "Position");
_colorSlot = glGetAttribLocation(programHandle, "SourceColor");
glEnableVertexAttribArray(_positionSlot);
glEnableVertexAttribArray(_colorSlot);
}
1. Uses the method you just wrote to compile the vertex and fragment shaders.
vertex 와 fragment shader 를 컴파일하기 위해서 좀전에 작성한 메소드를 사용한다.
2. Calls glCreateProgram, glAttachShader, and glLinkProgram to link the vertex and fragment shaders into a complete program.
glCreateProgram, glAttachShader, glLinkProgram 함수를 호출해서 vertex 와 fragment shader 를 연결하고
program 안에 넣는다.
3. Calls glGetProgramiv and glGetProgramInfoLog to check and see if there were any link errors, and display the output and quit if so.
glGetProgramiv, glGetProgramInfoLog 함수를 호출해서 링크 에러가 있는지 확인한다
4. Calls glUseProgram to tell OpenGL to actually use this program when given vertex info.
glUseProgram 함수를 호출해서 꼭짓점 정보가 주어졌을때 OpenGL 에게 실제적으로 이 program 을 사용하라고 알린다.
5. Finally, calls glGetAttribLocation to get a pointer to the input values for the vertex shader, so we can set them in code.
Also calls glEnableVertexAttribArray to enable use of these arrays (they are disabled by default).
마지막으로 glGetAttribLocation 함수를 호출해서 vertex shader 의 입력변수에 대한 포인터를 얻는다.
또한 glEnableVertexAttribArray 함수를 호출해서 이 배열들을 활성화한다 (기본적으로 비활성화 되어 있다)
Two last steps. Add the following method call to initWithFrame right before calling render:
마지막 2단계는. initWithFrame 메소드 안에서 render: 를 호출하기 전에 다음과 같이 compileShaders 를 호출한다
[self compileShaders];
And declare the instance variables for _colorSlot and _positionSlot inside the @interface in OpenGLView.h as follows:
그리고 _colorSlot 과 _positionSlot 이라는 instance 변수를 OpenGLView.h 에 선언한다.
GLuint _positionSlot;
GLuint _colorSlot;
You can compile and run now, and if it displays a green screen (and does not quit with an error),
it means that your vertex and fragment shaders compiled OK at runtime!
이제 컴파일 하고 실행해라, 초록색 화면이 보이면(에러없이)
앱이 실행중일때, 너의 vertex shader 와 fragment shader 가 컴파일 성공했다는 뜻이다.
Of course, nothing looks differnet because we haven’t given the shaders any vertex geometry to render.
So let’s take care of that next.
물론 아무런 3차원 꼭짓점 데이타를 넘기지 않았기 때문에, 화면상에는 달라진점이 없다.
* Creating Vertex Data for a Simple Square
Let’s start things nice and simple by rendering a square to the screen. The square will be set up like the following:
화면에 그릴 사각형을 시작하자. 사각형은 다음과 같이 설정될것이다.
When you render geometry with OpenGL,
keep in mind that it can’t render squares – it can only render triangles.
However we can create a square with two triangles as you can see in the picture above:
one triangle with vertices (0, 1, 2), and one triangle with vertices (2, 3, 0).
OpenGL 로 3차원물체를 그릴때, 사각형은 그릴수 없다는걸 기억해라. OpenGL 은 삼각형만 그릴수 있다.
위의 사진에서 보듯이 2개의 삼각형으로 사각형으로 만들수 있다.
삼각형 하나의 꼭짓점은 0,1,2 이고, 다른 삼각형은 2,3,0 이다.
One of the nice things about OpenGL ES 2.0 is you can keep your vertex data organized in whatever manner you like.
Open up OpenGLView.m and create a plain old C-structure and a few arrays to keep track of our square information, as shown below:
OpenGL ES 2.0 의 좋은점중 하나는 꼭짓점 데이타의 구조를 내맘대로 할수 있다는 것이다.
OpenGLView.m 파일을 열고 아래에 보이는 것처럼 c 구조체를 만들고 배열몇개를 만든다 (사각형 정보)
typedef struct {
float Position[3];
float Color[4];
} Vertex;
const Vertex Vertices[] = {
{{1, -1, 0}, {1, 0, 0, 1}},
{{1, 1, 0}, {0, 1, 0, 1}},
{{-1, 1, 0}, {0, 0, 1, 1}},
{{-1, -1, 0}, {0, 0, 0, 1}}
};
const GLubyte Indices[] = {
0, 1, 2,
2, 3, 0
};
So basically we create:
a structure to keep track of all our per-vertex information (currently just color and position)
기본적으로 만들것은
꼭짓점마다의 정보(현재는 색과 위치)를 저장할 구조체
an array with all the info for each vertex
각 꼭짓점의 정보를 담을 배열
an array that gives a list of triangles to create, by specifying the 3 vertices that make up each triangle
삼각형을 만들때 쓰일 3개의 꼭짓점을 기술한 배열
We now have all the information we need, we just need to pass it to OpenGL!
필요한 모든 정보가 있다, 이제 OpenGL 에게 넘겨주면 된다.
* Creating Vertex Buffer Objects
The best way to send data to OpenGL is through something called Vertex Buffer Objects.
OpenGL 에게 데이타를 전달하는 가장 좋은 방법은, Vertex Buffer Objects 라고 불리는 것이다.
Basically these are OpenGL objects that store buffers of vertex data for you.
You use a few function calls to send your data over to OpenGL-land.
기본적으로 OpenGL 에는 꼭짓점 데이타를 담을 버퍼 객체가 있다.
몇개의 함수를 호출해서 데이타를 OpenGL 에게 넘겨줄수 있다.
There are two types of vertex buffer objects – one to keep track of the per-vertex data (like we have in the Vertices array),
and one to keep track of the indices that make up triangles (like we have in the Indices array).
vertex buffer objects 는 2가지 종류가 있는데
하나는 꼭짓점마다의 데이타를 저장하는 곳이고(아까 만든 Vertices 배열처럼)
또 하는 삼각형을 만들 인덱스를 저장할 곳이다(아까 만든 Indices 배열처럼)
So add a method above initWithFrame to create these:
initWithFrame 메소드 위에 아래처럼 메소드를 추가해라:
- (void)setupVBOs {
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
GLuint indexBuffer;
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}
You can see that it’s pretty simple. It uses a similar pattern that you’ve seen before –
calls glGenBuffers to create a new Vertex Buffer object, glBindBuffer to tell OpenGL “hey when I say GL_ARRAY_BUFFER,
I really mean vertexBuffer”,
and glBufferData to send the data over to OpenGL-land.
간단하다, 전에 보았던것과 비슷하다
glGenBuffers 함수를 호출해서 새로운 Vertex Buffer object 를 만들고
glBindBuffer 함수는 "내가 GL_ARRAY_BUFFER 라고 하면 vertexBuffer 라는 뜻인줄 알아라" 라고 OpenGL 에게 알려준다
그리고 glBufferData 는 OpenGL 에게 데이타를 전달한다.
And before we forget, add this to initWithFrame right before calling render:
initWithFrame 의 render: 를 호출하기 전에 아래의 문장을 넣어라
[self setupVBOs];
* Updated Render Code
We have all of the pieces in place,
finally we can update the render method to draw our new vertex data to the screen, using our new shaders!
Replace render with the following:
이제 모든 준비가 됬다.
마지막으로 새로운 꼭짓점 데이타를 화면에 그리기 위해서 render 메소드를 수정할것이다. 새로운 쉐이더를 이용해서
render 메소드를 아래와 같이 고쳐라
- (void)render {
glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// 1
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// 2
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
// 3
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
This works as follows:
1. Calls glViewport to set the portion of the UIView to use for rendering.
This sets it to the entire window, but if you wanted a smallar part you could change these values.
glViewport 함수를 호출해서 rendering 에 사용할 UIView 의 부분을 설정한다.
(이석우 추가 : UIView 의 특정부분에만 그리겠다는 뜻)
여기서는 윈도우 전체로 설정했지만, 작은 부분만 설정하기 원한다면 이 값을 바꿀수 있다.
2. Calls glVertexAttribPointer to feed the correct values to the two input variables for the vertex shader
– the Position and SourceColor attributes.
This is a particularly important function so let’s go over how it works carefully.
The first parameter specifies the attribute name to set. We got these earlier when we called glGetAttribLocation.
The second parameter specifies how many values are present for each vertex. If you look back up at the Vertex struct,
you’ll see that for the position there are three floats (x,y,z) and for the color there are four floats (r,g,b,a).
The third parameter specifies the type of each value – which is float for both Position and Color.
The fourth parameter is always set to false.
The fifth parameter is the size of the stride, which is a fancy way of saying “the size of the data structure containing the per-vertex data”.
The final parameter is the offset within the structure to find this data.
The position data is at the beginning of the structure so we can pass 0 here,
the color data is after the Position data (which was 3 floats, so we pass 3 * sizeof(float)).
glVertexAttribPointer 함수를 호출해서 vertex ahder 의 2개의 입력변수(Position, SourceColr)에 올바른 값을 제공한다
이것은 어떻게 동작하는지 주의깊에 봐야할 중요한 함수이다.
첫번째 파라메터는 attribute 명의 참조를 기술한다
(전에 glGetAttribLocation 함수를 호출하여 vertex shader 에 선언된 attribute 변수의 참조를 얻었었다)
두번째 파라메터는 하나의 꼭짓점에 몇개의 값이 제공되는지 기술한다.
앞의 Vertex 구조체를 보면 position 은 3개의 float 변수(x,y,z) 로 이루어져 있고, color 는 4개의 float 변수(r,g,b,a) 로 이루어져있다.
세번째 파라메터는 vertex shader 에 넘길 데이타의 타입을 기술한다.
네번째 파라메터는 항상 false 로 설정해라.
So we can simply pass in sizeof(Vertex) here to get the compiler to compute it for us.
다섯번째 파라메터는 뛰어넘을 사이즈를 넣어라, 하나의 꼭짓점당 사용되는 구조체 데이타의 크기이다.
마지막 파라메터는 구조체에서 이 데이타가 나타나는 시작점이다.
position 데이타는 구조체에서 첫부분에 아오므로 0으로 넣고,
color 데이타는 position 데이타 뒤에 나오므로 3 * sizeof(float) 으로 넣었다.
Ok back to the final part of the code listing!
소스의 마지막으로 돌아가자
3. Calls glDrawElements to make the magic happen!
This actually ends up calling your vertex shader for every vertex you pass in,
and then the fragment shader on each pixel to display on the screen.
This is also an important function so let’s discuss each parameter here as well.
The first parameter specifies the manner of drawing the vertices.
There are different options you may come across in other tutorials like GL_LINE_STRIP or GL_TRIANGLE_FAN,
but GL_TRIANGLES is the most generically useful (especially when combined with VBOs) so it’s what we cover here.
glDrawElements 함수를 호출하면 신기한 일이 생긴다.
이 함수는 결국 너가 넘긴 모든 꼭짓점마다 vertex shader 를 호출하게 하고
화면에 그릴 각각의 픽셀마다 fragment shader 가 호출하게 한다.
이거 역시 중요한 함수니까, 각각의 파라메터에 대해서 논의하자
첫번째 파라메터는 꼭짓점들을 그리는 방식을 기술한다.
몇가지 옵션이 있다. 하지만 GL_TRIANGLES 는 일반적으로 사용되는 옵션이다(특별히 VBO랑 어울려서)
The second parameter is the count of vertices to render.
We use a C trick to compute the number of elements in an array here by dividing the sizeof(Indices) (which gives us the size of the array in bytes) by sizeof(Indices[0]) (which gives us the size of the first element in the arary).
두번째 파라메터는 꼭짓점들의 갯수다. sizeof(Indices)/sizeof(Indices[0])
이석우 추가 : 개발자라면 알거 같아서, 간략한 설명은 생략한다.
The third parameter is the data type of each individual index in the Indices array.
We’re using an unsigned byte for that so we specify that here.
세번째 파라메터는 index 각각의 데이타 타입이다.
From the documentation, it appears that the final parameter should be a pointer to the indices.
But since we’re using VBOs it’s a special case – it will use the indices array we already passed to OpenGL-land in the GL_ELEMENT_ARRAY_BUFFER.
문서에 따르면 마지막 파라메터는 index들의 포인터여야 한다.
하지만 우리는 VBO를 사용하기 때문에, (이미 index들의 배열을 opengl 에게 넘겨주었다) 여기서는 그냥 0
Guess what – you’re done! Compile and run the app and you should see a pretty rectangle on the screen:
끝났다. 컴파일하고 실행하면 화면에 이쁜 사각형이 보일것이다.
You may be wondering why this rectangle happens to fit perfectly on the screen.
Well by default, OpenGL has the “camera” at (0,0,0), looking down the z-axis.
The bottom left of the screen is mapped to (-1,-1), and the upper right of the screen is mapped to (1,1),
so it “stretches” our square to fit the entire screen.
Obviously, in a real app you’ll want to have more control over how the “camera” behaves.
So let’s talk about how you can do that with a projection transform!
화면에 사각형이 딱맞게 보이는게 이상할것이다.
기본적으로 opengl 은 0,0,0 좌표에 카메라를 가지고 있다.
화면의 좌하귀는 (-1,-1) 이고, 우상귀는 (1,1) 이다.
그래서, 우리의 사각형을 화면에 꽉차게 늘린다.
너는 앱에서 카메라의 행동을 조절하기 원할것이다.
projection transform (투영변형) 으로 그걸 어떻게 하는지 이야기 해보자.
To make objects appear 3D on a 2D screen, we need to apply a projection transform on the objects.
Here’s a diagram that shows how this works:
3차원 물체는 2차원의 스크린에 보여줄때는, 물체에 투영변형이 필요하다.
이 다이어그램은 이것을 설명해준다.
Basically we have a “near” plane and a “far plane”, and the objects we want to display are in-between.
The closer an object is to the “near” plane we scale it so it looks smaller,
and the closer and object is to the “far” plane we scale it so it appears bigger. This mimics the way a human eye works.
기본적으로 near plane (가까운면)과 far plane (먼면)을 가지고 있고,
우리가 보여주길 원하는 물체는 그 사이에 있다.
near plane 과 가까이에 있는 물체는 작게 줄이고, far plane 과 가까이에 있는 물체는 크게 늘린다.
이것이 인간의 눈을 흉내내는 방식이다.
Let’s see how we can modify our app to use a projection. Start by opening SimpleVertex.glsl, and make the following changes:
우리의 앱이 투영을 사용하도록 수정해보자, SimpleVertex.glsl 열고 다음과 같이 수정해라
// Add right before the main
uniform mat4 Projection;
// Modify gl_Position line as follows
gl_Position = Projection * Position;
Here we add a new input variable called Projection. Notice that instead of marking it as an attribute, we mark it as a uniform.
This means that we just pass in a constant value for all vertices, rather than a per-vertex value.
여기 새로운 입력변수 Projection 이 추가되었다.
attribute 대신에 uniform 으로 표시된걸 주목하자
이것은 모든 꼭짓점들의 요소에 상수값을 전달한다는 뜻이다. (모든 꼭짓점이 아니다)
Also note that the Projection is marked as a mat4 type.
Mat4 stands for a 4×4 matrix.
Matrix math is a big subject that we won’t really cover here,
but for now just think of them as things you can use to scale, rotate, or translate vertices.
We’ll pass in a matrix that moves our vertices around according to the Projection diagram above.
또한 Projection 이 mat4 타입이란것도 주목하자.
mat4 는 4*4dml 행렬이다.
수학에서 행렬이라는 주제는 거대해서, 여기서 다 다룰수 없다.
꼭짓점들의 크기변환, 회전, 변형에 사용할수 있다는것만 알아두자.
위에서 본 Projection diagram 에 따라서, 우리는 여기에 행렬을 넘겨줄것이다.
'OpenGLES 초보' 카테고리의 다른 글
[번역] All about OpenGL ES 2.x - (part 2/3) (0) | 2014.11.25 |
---|---|
frame buffer, render buffer, vbo (0) | 2014.11.11 |
OpenGL Shader Builder. mac (0) | 2014.10.28 |
blender, Smart UV Project (0) | 2014.07.04 |
적을 바라보기 (0) | 2014.07.04 |