:: 높이맵정보를 가지고 있는 이미지 파일(웹에올리기 위해 jpg로 확장자 변환하였다.)
먼저 33 * 33 타일을 만들었다
버텍스수는 33 * 33 = 1089가 되므로 픽셀수도 1089개 되어야함..
33 * 33 짜리 이미지는 포토샵에서 필터 - 렌더 - Difference Clouds 을 적용하여 만들었다.
이미지 - 적용 에서 그레이스케일로한후 save as 에서 .raw 확장자로 저장하게되면
한 픽셀당 8 비트가 할당된 이미지가 만들어지게된다(총 1089바이트)
색상이 흰색에 가까울수록 255에 가깝고 검은색에 가까울수록 0에 가깝다
이제, 이 픽셀들을 버텍스들과 매핑시킬때 버텍스의 y 좌표값에 이 바이트값을 넣어주면 높이가 표현된다.
맵이미지 파일을 읽어올때는 fopen() 과 fscanf() 를 사용해서 읽었다.
읽은후에는 버텍스버퍼 락을걸고 1089 번 루프를 돌면서 각 버텍스의 y성분만 건드려주면된다.
제법 간지나게 나온듯해서 뿌듯하다 -_-흐흐..
.. 그리고 몇가지 이슈사항..
1. D3DXCreateVertexBuffer9 로 버텍스 버퍼를 생성할때 D3DUSAGE 인자를 넣어야하는데..
높이맵처럼 버텍스의 특정 성분(y좌표)만 수정이 빈번하게 이루어진다면 어떤 플래그를 넣어야할까..
D3DUSAGE_WRITEONLY, D3DUSAGE_DYNAMIC(이걸로 해봤더니 버텍스버퍼에 쓰기할때 에러난다), 등등등..
플래그가 여러개가 있는데.. 뭘쓰는게 가장 최적화에좋을지..
2. 1바이트는 0 부터 255까지 나타낼수있는데 이 값을 그대로 버텍스의 Y성분에 적용해버리면 안되고
0.03 정도를 곱해서 전체적인 수치를 좀 낮춰줘야한다.. 왜냐하면.. 버텍스 간의 간격이 1로되어있는데
높이가 255면 아예 화면에 보이지도 않을정도로 커져버리기때문..
3D 세계의 맵을 구성하는 가장 기본적인 단위인 "타일" 이다.
아무리 거대한 맵이라도 이 타일이 합쳐져서 큰 맵하나가 되는것이다.
이제 여기에 Y (높이맵) 값만 적용해주면 그럴싸한 지형이 완성될것이다.!!
이 타일의 버텍스버퍼와 인덱스버퍼를 만드는 코드는 다음과 같이작성했다.
void Cdx3dView::initPlane()
{
int width = 15; // 가로 버텍스수 15개
int height = 15; //세로 버텍스수 15개
int total = width*height; // 총 버텍스수
float gap = 1.0f; //버텍스 간의 거리
PVertex arrTemp[225];
for(int i=0;i<width;i++) // 행
{
for(int j=0;j<width;j++) // 열
{
arrTemp[ j + i*width ].position = D3DXVECTOR3(gap*(float)j, 0.0f, gap*(float)i);
}
}
void *pTemp;
m_pd3dDevice->CreateVertexBuffer(sizeof(PVertex)*total, 0, PVertex::FVF, D3DPOOL_MANAGED, &planeVB, 0);
planeVB->Lock(0, sizeof(PVertex)*total, (void**)&pTemp, 0);
memcpy(pTemp, arrTemp, sizeof(arrTemp));
planeVB->Unlock();
int numPolygon = (width -1) * (width -1) * 2;
PIndex arrTemp2[392]; // 폴리곤 갯수만큼 인덱스 생성.
int cntPolygon = 0;
for(int i=0;i<width-1;i++)
{
if(cntPolygon >= numPolygon){ break; }
for(int j=0;j<width-1;j++)
{
//하단 폴리곤
arrTemp2[cntPolygon].a = i*width + j;
arrTemp2[cntPolygon].b = i*width + (j+1);
arrTemp2[cntPolygon].c = (i+1)*width + (j+1);
cntPolygon++;
//상단 폴리곤
arrTemp2[cntPolygon].a = i*width + j;
arrTemp2[cntPolygon].b = (i+1) * width + j;
arrTemp2[cntPolygon].c = (i+1) * width + (j+1);
cntPolygon++;
}
}
m_pd3dDevice->CreateIndexBuffer(sizeof(PIndex)*numPolygon, 0, D3DFMT_INDEX32, D3DPOOL_MANAGED, &planeIB, 0);
void *pIB;
planeIB->Lock(0, sizeof(arrTemp2), (void **)&pIB, 0);
memcpy(pIB, arrTemp2, sizeof(arrTemp2));
planeIB->Unlock();
}
D3DXLoadMeshFromX(....)를 호출하면 여러가지 정보를 프로그래머에게 넘겨주는데
이때 사용하는것이 ID3DXBuffer 이다
x파일로부터 메쉬를 읽어들일때, 그 메쉬의 메터리얼정보나 인접삼각형 정보까지 넘어오는데 이
정보들이 ID3DXBuffer 로 넘어온다.
ID3DXBuffer에 속한 버퍼의 타입이 결정되어있지 않으므로 명시적으로 타입을 정해야한다
받아올때 다음과같이 받아오면된다
매터리얼 정보를 받아온다고할때
ID3DXBuffer *buffer;
D3DXLoadMeshFromX( , , , ,&buffer , ) //다른 인수에대한 설명은 생략.
D3DXMATERIAL *mtrls = (D3DXMATERIAL *)(buffer -> GetBufferPointer());
mtrls[0].MatD3D // D3DMATERIAL9 구조체에 접근
mtrsl[0].pTextureFileName // 텍스쳐파일명(문자열)에 접근
이렇게 하면된다.
ID3DXBuffer 의 GetBufferPointer() 메서드는 ID3DXBuffer 가 관리하고있는 버퍼의 첫번째 메모리 블록주소를 리턴한다. 이것을 D3DXMATERIAL * 로 형변환을 하게되면 버퍼(배열)에 접근할때 오프셋이 달라지는효과가 나는것이다 D3DXMATERIAL 의 크기를 72바이트라고 가정할때, mtrls[0], mtrls[1] 둘의 메모리 블락수 차이는 72 가 되는 것이다. 즉 mtrls[1] 은 mtrls[0] 이 존재하는 메모리보다 72 블락 뒤에 있다는것이다.
하나의 메쉬에는 여러개의 서브셋이 존재할 수 있다.
서브셋이라는 개념이 필요한 이유가뭘까?
집이라는 오브젝트가 있다고해보자. 이 집은 지붕, 창문, 벽 으로 이루어져있다. 그런데 지붕, 창문, 벽의 재질이 모두 동일할까? 동일하지않다. 그래서 서브셋이라는 개념이 존재하는것이다. 집이라는 메쉬하나를 3개의 서브셋으로 분리해야만 각각의 메터리얼(재질)을 분리하여 적용할수있게된다.
텍스쳐를 적용할때도 마찬가지이다. 텍스쳐도 사실 개념적으로 메터리얼이기때문에 다이렉트X 에서
다음과같은 구조체를 만들어놓은거라고 생각한다.
D3DXMATERIAL
이 구조체를 까보면
두개의 속성이 있다
MatD3D : D3DMATERIAL9 구조체와 동일한 구조를 가진 구조체이다.
Ambient, Diffuse, Specular, Emissive, Power 의 속성을 가졌다.
pTextureFileName : 문자열이며 텍스쳐 이미지의 파일명을 나타낸다.
x파일(맥스에서 작업한)을 읽어올때 D3DXMATERIAL 타입의 배열을 받아올수있는데,(매터리얼 버퍼라고 한다)
이 배열의 길이만큼 for문을 돌려서 메쉬를 그리면 된다.
코드로 간략하게 나타내보자.
D3DXMATERIAL materials[3]; //길이 3의 메터리얼 버퍼가 존재한다고 가정.
for(int i=0;i<3;i++){
device -> SetMaterial(materials[i].MatD3D);
// materials[i].pTextureFileName 을 참조하여 텍스쳐 객체(ID3DXTexture 였나?) 생성
device -> SetTexture(....);
mesh -> DrawSubset(i);
}
이 모든 설명을 2줄로 요약하면..
메터리얼과 서브셋은 1:1 대응관계이다.
즉, 메터리얼 1개에 대응되는 서브셋은 1개 !!! , 서브셋 1개에 대응되는 메터리얼도 1개 !!!
지식인 QnA 펌
질문
decoding.exe의 0x00413f9d에 처리되지 않은 예외가 있습니다. 0xC0000005: 0x00000000 위치를 기록하는 동안 액세스 위반이 발생했습니다. 엑세스 위반이라는 것으로 실행이 안되는데요.
0x00413f9d 라는 부분은 어떻게 해석을 해야 할지와 엑세스 위반이라는 것은 무엇인지
답변 부탁드리겠습니다~(__)
답변
엑세스 위반(Access Violation)은, 프로세스가 접근할 권한이 없는 메모리 영역에 접근하고자 했을 때 발생합니다.
윈도우는 메모리의 모든 영역을 가상메모리 페이지 단위로 관리하는데, 이 메모리 영역마다 접근 권한을 설정합니다. 이 권한은 프로세스가 가진 속성별로 관리됩니다. 크게 사용자가 만든 사용자 프로세스와, 윈도우 시스템에서 돌리는 커널 프로세스로 구분할 수 있죠. 윈도우에서 관리하는 메모리는 커널에서만 읽고 쓸수 있는 영억, 커널에서만 읽을 수 있는 영억, 사용자프로세스에서도 읽을 수 있는 영역, 사용자 프로세스가 읽고 쓸 수 있는 영역 등으로, 가상메모리 페이지에 권한이 설정되어 있습니다.
따라서 적절한 권한이 없는 프로세스가 제한된 메모리 영역에 읽고 쓰기를 시도할 때 엑세스 위반이 발생합니다. 대표적인 예로 "0xC0000005: 0x00000000 위치를 기록하는 동안 엑세스 위반이 발생했습니다"라는 예외는 0x00000000 번지, 즉 널 포인터에 대하여 쓰기를 시도했다는 뜻이죠. 0x00000000번지로 시작하는 메모리 영역은 유저 프로세스에 대해서는 읽기/쓰기가 금지된 영역입니다. 시스템 운영에 중요한 정보가 기록되어 있기 때문이죠. 따라서 사용자가 만든 프로그램이 이 위치를 접근하고자 하는 것은 이와 같은 에러를 냅니다.
에러 메시지에서, "decoding.exe의 0x00413f9d에 처리되지 않은 예외가 있습니다. 0xC0000005: 0x00000000 위치를 기록하는 동안 액세스 위반이 발생했습니다."의 0x00413f9d는, 0x00000000위치를 접근하고자 하는 명령이 수행된, 명령이 저장된 번지수입니다. 디버깅할 때 해당 번지수의 명령이 무엇인가를 살펴보면 비교적 헤메지 않고 버그를 잡을 수 있습니다.
답변
이런 경우는 대부분..
메모리 할당이 되지 않은 포인터(NULL Pointer)에 값을 넣었거나..
할당치를 초과하여 데이터가 입력되었을 때입니다..
답변
0x00413f9d 는 decoding.exe 이란 프로그램의 code 영역 주소입니다.
즉 decoding.exe의 0x00413f9d 주소에서 예외처리가 발생했다는것입니다.
프로그램은 대부분 0x00400000이 시작번지이므로
offset 0x00013f9d에서 0xc0000005 에러가 발생했다는것입니다.
답변
0x00413f9d는 메모리의 주소를 말하는 것으로, 동적 메모리를 할당하지 않았거나
할당항 메모리를 넘어서 접근 할 경우에 이러한 에러가 나게 됩니다.
int *temp;
temp[0] = 10; // 메모리 에러(할당하지 않고 사용한 경우)
int *temp1;
temp1 = new int [10];
temp1[15] = 10; //메모리 에러(할당은 하였지만 할당항 메모리를 넘어서 사용한 경우
대부분의 C언어책에서
define은 "단순 치환"의 기능을 가졌다고한다.
예를들어
#define PI 3.14
즉 PI 가 3.14를 가리킨다는 것이다.
물론 맞는 말이다. 하지만 define이 존재하는 근본적인 이유가 몇가지 있다.
1. RAM 의 메모리에 할당되지 않는다.
int a = 1;
이 코드 한줄이 실제로 어떻게수행되는지 보자.
1) 램영역에 4바이트의 메모리가 할당된다.
2) CPU는 이 할당된 영역에 0000 0000 0000 0000 0000 0000 0000 0001 이라는 데이타를 써넣는다.
#define a = 1;
하지만 이렇게 #define을 사용한다면 램영역에 메모리 할당은 없다.
코드내의 a 는 1이라는 상수값으로 치환되어 컴파일 되는것이다.
요약하면,
전자는 메모리 할당이 일어나는 반면에, 후자는 메모리할당이 일어나지않는다.컴파일타임에 상수로 취급되어 버리기때문이다.
define을 썼을때의 장점이 느껴지는가?
1. 메모리 절약
2. 코드의 가독성 증가.
하지만 조금이라도 값이 바뀔 가능성이 존재한다면 define을 사용해서는 절대로 안된다.
물론 컴파일도 안될것이다.
define은 단지 상수를 위해 존재한다.
LCD 모듈을 에로들어보자.
LCD 는 AVR 입장에서볼때 외부 I/O 장치이다.
즉 LCD 모듈에 접근하려면 어드레스버스와 데이타버스가 필요하다는 것이다.
AVR에서는 A0 ~ A15 까지(총 16개의 비트)를 어드레스 버스로 사용하고있다.
A0~A7 은 데이타버스로도 사용된다.
8비트 프로세서에서 어드레스버스가 16비트나 되는 이유가뭘까?
내생각으로는, 확장성을 위해서인것 같다. 어드레스 버스가 8비트밖에 되지않는다면
접근할수 있는 주소가 256개 밖에 되지않을것이다. 그러나 16비트라면 65536 개의 주소에 접근할 수 있다.
그만큼 외부 장치를 많이 연결할 수 있다는 말이다.
이 어드레스버스에 주소값을 넣으면 디코딩 회로는 이 주소값을 해석해서 하드웨어적으로 이 주소에 해당하는곳
을 활성화 시킨다. 이제 데이타버스는 이 활성화된 영역에 데이타를 쓰고, 읽을수가 있게된것이다.
이 메커니즘은 일반 PC 에서 CPU가 RAM 영역에 접근할때도 사용된다.
메모리의 주소값을 담은 어드레스버스가 디코딩회로에 의해 해석되면 데이타버스가 그 주소로 이동하고
이제 이 데이타버스에 데이타를 써넣으면 램에 데이타가 써지는 것이다.
/* 이글은 걍 개인적으로 공부하면서 한번더 이해하기 위해 쓴글이니 설명이 난해하다고 뭐라하지마세요 ㅜ.ㅜ */
타이머는 avr 칩 내부에서 동작하는 넘이다. 그래서 i/o 포트와는 관련이 없음
Atmega128 에는 타이머가 총 4개가 존재한다.
타이머0 ,타이머1, 타이머2, 타이머3
이렇게 있는데, 타이머0, 타이머2는 8비트짜리고 타이머1, 타이머3은 16비트 짜리다.
여기서 비트가 의미하는건 최대 카운트 수이다.
8비트는 256까지 나타낼수 있으므로
타이머가 256까지 카운트를 세었을때 "타이머 오버플로우 인터럽트"가 발생하는것이고
16비트는 65536 까지 나타낼수 있으므로
타이머가 65536 까지 카운트를 세었을때 "타이머 오버플로우 인터럽트"가 발생하는 것이다.
타이머 인터럽트를 사용하기전에 설정해야할 레지스터들이 몇개있다
1. TCCR (Timer/Counter Control Register)
카운터가 발생하는주기(프리스케일러 라고함) 를 설정할 뿐만 아니라 카운터의 전반적인것에 대해서 설정할 수 있다. 자세하게 들어가면 골치아프므로(나도 자세히는모름) 가장 많이쓰이는 프리스케일러 설정만 살펴보자
타이머1 에 대한 Control TCCR은 3개나 존재하더라..
TCCR1A, TCCR1B, TCCR1C (A,B,C 를 접미어로 붙여서 구분하는듯)
/* Timer1의 Compare 인터럽트 설정하는 레지스터인데 지금하는건
Timer Overflow 관련부분이니까 패스. 즉 0x00 으로 셋팅함으로써 사용안함을 명시. */
TCCR1A = 0x00;
/*
TCCR1B 하위 3비트(0비트, 1비트, 2비트)에서 프리스케일러를 설정한다.
1을 왼쪽으로 3번 비트연산한다는뜻. (CS12가 3을 나타내는듯함)
데이타시트 찾아보니까 100(2) 은 64 clk 를 나타내네. 즉 64 클록당 카운트 한개 증가시킨다는 소리.
64클록마다 카운트 한개씩 증가시켜서 타이머카운트 레지스터(TCNT1)값이 65536이 되었을때 인터럽트
발생함. 대략 몇 ms 걸릴까? 계산하기 귀찮다 -_-;
*/
TCCR1B = 1 << CS12;
/*
이 레지스터도 0x00으로 셋팅해서 사용안함을 명시.
*/
TCCR1C = 0x00;
타이머1의 전반적인 설정을 끝냈으니
이제 타이머1 인터럽트를 인에이블(사용가능하게) 설정해야한다.
이것을 담당하는 레지스터는 TIMSK(Timer Interrupt Mask) 이다.
TIMSK = 1 << TOIE1;
이렇게 해주면 타이머1의 "타이머 오버플로우 인터럽트"가 인에이블 상태로된다.
참고로 TOIE는 Timer Overflow Interrupt Enable 의 약자이다.
이 TOIE1 비트는 TIMSK 레지스터의 하위 3번째 비트(비트번호 2)에 위치하고있다. 그래서
1 << TOIE1 하면 0000 0100; 이라는 결과값이 나와서 TIMSK 레지스터의 TOIE1 비트에 1이 셋팅되는듯.
TIMSK 는 총 8비트로 이루어져있는데 OCIEx(Output Compare Interrupt Enable) 와 TOIEx 로구성되어있다. 컴페어 인터럽트는 아직 안써봤으므로 언급하지않겠다...
이제 TIMSK 까지 설정했으면 타이머 인터럽트를 동작시키기위한 셋팅은 거의 끝났다고 볼수있다
그러나 한가지 더 해줘야할것이 있다!
바로 SREG(맞나?) 라는 상태레지스터의 8번쨰비트인 I-플래그 를 1로 셋팅해줘야한다.
sei() 라는 함수를 호출해주면 내부적으로 셋팅이된다.
cli(); // 전역 I-플래그를 클리어한다. 0 으로 셋팅하는듯
sei(); //전역 I-플래그를 셋한다. 1로 셋팅하는듯
자 이제 여기까지했으면 타이머 인터럽트가 동작하기 위한 모든준비가 끝난것이다!
코드까지 쓰려고했지만...-_- 글이너무 길어질것 같아서 오늘은 여기까지..
PMP에 들어갈 모듈중 하나인 "파일브라우저" 하나를 구현하는데 벌써 클래스만 20개가 다되간다.
앞으로 구현해야할 모듈이 6개는 더되는데...
그래도 일단 파일브라우저 하나는 어느정도 끝이보이는지라... 중간 작업물을 올려본다.
그리고.. 몇가지 골치아팠던 부분...
1. Native c/c++ 와 통신을 하는지라 동기화를 철저히 해야했다.
Native c/c++ 에서 던져주는 데이타들이 이벤트 기반이기때문에(block방식이 아님) 동기화에 상당히 많은 신경을 써야했다. 객체의 라이프사이클을 철저하게 관리해서 이 난제를 극복했다. 즉, 객체의 초기화와 그 이후의 행동에 대해서 철저하게 분리하여 프로그래밍 하는것이다. 또한 각 기능별로 매니저 클래스를 둠으로써 코드들을 깔끔하게 분리할 수 있었다. CDialogMgr, CFbMgr이 매니저 클래스이다.
매니저 클래스에서는 주로 Native 함수를 호출하고, 데이타를 전달받아 스크립트단 버퍼에 채워넣는 일을 한다.
만약 이러한 코드들이 매니저클래스없이 한클래스안에 묶여져있었다면 코드가 난잡해져서 가독성이 현저하게
떨어졌을 것이다.
2. 스크롤바.
파일브라우저에서 리스들은 페이지단위로 스크롤 되어야하기떄문에 스크롤바또한 이것에 맞춰져야했다.
"스냅"(딱딱 달라붙는 듯한효과)방식으로 스크롤바를 만들었다.리스트가 갱신될때마다 총 페이지수를 구한후
스크롤바의 reInit(numPages) 함수를 호출하여 스크롤바또한 리프레쉬를 시켜준다.
리스트의 총페이지 수만큼 스크롤바의 스냅 포지션의 수가 정해진다. 이 스냅포지션은 arrPos라는 배열에 할당되며 사용자가 스크롤할때마다 arrPos의 포지션값중 가장 가까운 값을 찾아 그 위치로 스냅하게된다.
스크롤이벤트가 발생되면 스크롤바는 이벤트를 발생시켜 파일브라우저 리스트에 타겟 페이지넘버를 넘겨준다.
파일브라우저는 넘겨받은 타겟 페이지넘버를 가지고 보여져야할 파일객체의 시작인덱스를 계산하여
스크립트단 버퍼에 접근하여 파일객체들을 배열형태로 가지고온후에 리스트화면에 렌더링한다.

이올린에 북마크하기
이올린에 추천하기
Prev
Rss Feed