在MTKView上渲染MTLTexture不保持纵横比

时间:2018-01-02 16:59:53

标签: ios swift metal metalkit

我的纹理是1080x1920像素。而我正试图在import pygame,sys,os,random from pygame.locals import * from cPickle import load from asyncore import write #initalize pygame pygame.init() #define colors black=(0,0,0) white=(255,255,255) def terminate(): pygame.quit() sys.exit() def drawText(text,font,screen,x,y,color): textobj=font.render(text,True,color) textrect=textobj.get_rect(center=(x,y)) screen.blit(textobj,textrect) class button(object): def __init__(self,x,y,width,height,text_color,background_color,text): self.rect=pygame.Rect(x,y,width,height) self.image=pygame.draw.rect(screen, background_color,(self.rect),) self.x=x self.y=y self.width=width self.height=height self.text=text self.text_color=text_color def check(self): return self.rect.collidepoint(pygame.mouse.get_pos()) def draw(self): drawText(self.text,font,screen,self.x+self.width/2,self.y+self.height/2,self.text_color) pygame.draw.rect(screen,self.text_color,self.rect,3) def dodger(screen,clock): global button #define variables white=(255,255,255) black=(0,0,0) fps=40 baddieminsize=10 baddiemaxsize=40 baddieminspeed=1 baddiemaxspeed=8 addnewbaddierate=6 player_speed=5 def terminate(): pygame.quit() sys.exit() def waitForPlayerToPressKey(): while True: for event in pygame.event.get(): if event.type==QUIT: terminate() if event.type==KEYDOWN: if event.key==K_ESCAPE: # pressing escape quits terminate() return def playerHasHitBaddie(playerRect, baddies): for b in baddies: if playerRect.colliderect(b['rect']): return True return False def drawText(text, font, surface, x, y): textobj = font.render(text, 1, white) textrect = textobj.get_rect() textrect.topleft = (x, y) surface.blit(textobj, textrect) def load_hs(): try: f = open("dodger_hs.txt","rb") hs = int(f.read()) f.close() return hs except: return 0 def write_hs(hs): f = open("dodger_hs.txt","wb") f.write(str(hs).encode()) f.close() # set up fonts font = pygame.font.SysFont(None, 90) # set up images playerImage = pygame.image.load('player.png') playerRect = playerImage.get_rect() baddieImage = pygame.image.load('baddie.png') # show the start screen done=False while not done: screen.fill(black) text_width,text_height=font.size("Dodger") drawText('Dodger', font, screen, (screen_width / 2-(text_width/2)), (screen_height / 2-200)) font = pygame.font.SysFont(None, 45) start_button=button(screen_width/2-125,175,250,50,white,black,'Start') start_button.draw() instructions_button=button(screen_width/2-125,250,250,50,white,black,'Instructions') instructions_button.draw() back_button=button(screen_width/2-125,325,250,50,white,black,'Back') back_button.draw() pygame.display.flip() while not done: for event in pygame.event.get(): if event.type==QUIT: terminate() elif event.type == pygame.MOUSEBUTTONDOWN: if start_button.check()==True: while not done: # set up the start of the game baddies = [] score = 0 playerRect.topleft = (screen_width / 2, screen_height- 50) moveLeft = moveRight = moveUp = moveDown = False reverseCheat = slowCheat = False baddieAddCounter = 0 high_score=load_hs() while True: # the game loop runs while the game part is playing score += 1 # increase score for event in pygame.event.get(): if event.type == QUIT: terminate() if event.type == KEYDOWN: if event.key == ord('z'): reverseCheat = True if event.key == ord('x'): slowCheat = True if event.key == K_LEFT or event.key == ord('a'): moveRight = False moveLeft = True if event.key == K_RIGHT or event.key == ord('d'): moveLeft = False moveRight = True if event.key == K_UP or event.key == ord('w'): moveDown = False moveUp = True if event.key == K_DOWN or event.key == ord('s'): moveUp = False moveDown = True if event.type == KEYUP: if event.key == ord('z'): reverseCheat = False if event.key == ord('x'): slowCheat = False if event.key == K_ESCAPE: terminate() if event.key == K_LEFT or event.key == ord('a'): moveLeft = False if event.key == K_RIGHT or event.key == ord('d'): moveRight = False if event.key == K_UP or event.key == ord('w'): moveUp = False if event.key == K_DOWN or event.key == ord('s'): moveDown = False # Add new baddies at the top of the screen, if needed. if not reverseCheat and not slowCheat: baddieAddCounter += 1 if baddieAddCounter == addnewbaddierate: baddieAddCounter = 0 baddieSize = random.randint(baddieminsize, baddiemaxsize) newBaddie = {'rect': pygame.Rect(random.randint(0, screen_width-baddieSize), 0 - baddieSize, baddieSize, baddieSize), 'speed': random.randint(baddieminspeed, baddiemaxspeed), 'surface':pygame.transform.scale(baddieImage, (baddieSize, baddieSize)), } baddies.append(newBaddie) # Move the player around. if moveLeft and playerRect.left > 0: playerRect.move_ip(-1 * player_speed, 0) if moveRight and playerRect.right < screen_width: playerRect.move_ip(player_speed, 0) if moveUp and playerRect.top > 0: playerRect.move_ip(0, -1 * player_speed) if moveDown and playerRect.bottom < screen_height: playerRect.move_ip(0, player_speed) # Move the baddies down. for b in baddies: if not reverseCheat and not slowCheat: b['rect'].move_ip(0, b['speed']) elif reverseCheat: b['rect'].move_ip(0, -5) elif slowCheat: b['rect'].move_ip(0, 1) # Delete baddies that have fallen past the bottom. for b in baddies[:]: if b['rect'].top > screen_height: baddies.remove(b) if score>=high_score: high_score=score # Draw the game world on the window. screen.fill(black) # Draw the player's rectangle screen.blit(playerImage, playerRect) # Draw each baddie for b in baddies: screen.blit(b['surface'], b['rect']) # Draw the score drawText('Score: %s' % (score), font, screen, 10, 0) drawText('High Score: %s' % (high_score), font, screen, 10, 30) pygame.display.update() # Check if any of the baddies have hit the player. if playerHasHitBaddie(playerRect, baddies): break clock.tick(fps) write_hs(high_score) screen.fill(black) if score<100: drawText('Your Score: %s' % (score), font,screen,screen_width/2-107.5, 185) if score<1000 and score>99: drawText('Your Score: %s' % (score), font,screen,screen_width/2-117.5, 185) if score<10000 and score>999: drawText('Your Score: %s' % (score), font,screen,screen_width/2-127.5, 185) if score<100000 and score>9999: drawText('Your Score: %s' % (score), font,screen,screen_width/2-127.5, 185) font = pygame.font.SysFont(None, 90) text_width,text_height=font.size("Game Over") drawText('Game Over', font, screen, (screen_width / 2-(text_width/2)), (screen_height / 2-200)) font = pygame.font.SysFont(None, 45) retry_button=button(screen_width/2-125,250,250,50,white,black,'Retry') retry_button.draw() back_button.draw() pygame.display.flip() back=False while not back: for event in pygame.event.get(): if event.type==QUIT: terminate() elif event.type == pygame.MOUSEBUTTONDOWN: if retry_button.check()==True: back=True if back_button.check()==True: back=True done=True elif instructions_button.check()==True: screen.fill(black) drawText('Your Score:', font,screen,screen_width/2-127.5, 185) back_button.draw() done=False while not done: for event in pygame.event.get(): if event.type==QUIT: terminate() elif back_button.check()==True: done=True elif back_button.check()==True: done=True #define other varibles clock=pygame.time.Clock() font=pygame.font.SysFont(None,40) done=False #set up screen screen_width=600 screen_height=600 screen=pygame.display.set_mode([screen_width,screen_height]) pygame.display.set_caption('The Arcade') #set up buttons dodger_button=button(25,75,125,50,white,black,'Dodger') #main loop while not done: for event in pygame.event.get(): if event.type==QUIT: terminate() elif event.type == pygame.MOUSEBUTTONDOWN: if dodger_button.check(): dodger(screen, clock) #fill screen with background screen.fill(black) #draw buttons dodger_button.draw() #draw text drawText('The Arcade', font, screen, screen_width/2, 25, white) #change display pygame.display.flip() clock.tick(15) terminate() 上呈现不同的宽高比。 (即iPad / iPhone X全屏)。

这就是我为MTKView

渲染纹理的方法
MTKView

我希望纹理在private func render(_ texture: MTLTexture, withCommandBuffer commandBuffer: MTLCommandBuffer, device: MTLDevice) { guard let currentRenderPassDescriptor = metalView?.currentRenderPassDescriptor, let currentDrawable = metalView?.currentDrawable, let renderPipelineState = renderPipelineState, let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: currentRenderPassDescriptor) else { semaphore.signal() return } encoder.pushDebugGroup("RenderFrame") encoder.setRenderPipelineState(renderPipelineState) encoder.setFragmentTexture(texture, index: 0) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) encoder.popDebugGroup() encoder.endEncoding() // Called after the command buffer is scheduled commandBuffer.addScheduledHandler { [weak self] _ in guard let strongSelf = self else { return } strongSelf.didRender(texture: texture) strongSelf.semaphore.signal() } commandBuffer.present(currentDrawable) commandBuffer.commit() } 上呈现为.scaleAspectFill,我正在尝试学习UIView所以我不确定我应该在哪里寻找这个( Metal文件,管道,视图本身,编码器等。)

谢谢!

编辑:这是着色器代码:

.metal

2 个答案:

答案 0 :(得分:2)

在处理金属纹理或金属时,一般要做的一些事情:

  • 您应该考虑 points pixels 之间的区别,请参阅文档here。 UIView子类的frame属性(如MTKView为1)总是在 points 中为您提供视图的宽度和高度。

  • 通过contentScaleFactor选项控制从点到实际像素的映射。 MTKView会自动选择具有与设备实际像素匹配的纵横比的纹理。例如,iPhone X上MTKView的底层纹理的分辨率为2436 x 1125(实际显示尺寸,以像素为单位)。这是记录here“MTKView类自动支持本机屏幕缩放。默认情况下,视图当前drawable的大小始终保证与视图本身的大小相匹配。” < / p>

  • 如此处所述,.scaleAspectFill选项“缩放[s]内容以填充视图的大小。内容的某些部分可能会被剪裁以填充视图的边界”。您想模拟此行为。

  • 使用Metal渲染只不过是“绘制”到解析纹理,它由MTKView自动设置。但是,您仍然可以完全控制并可以通过手动创建纹理并在renderPassDescriptor中设置纹理来自行完成。但是你现在不需要关心这一点。您应该关注的一件事是您希望在分辨率纹理中渲染的分辨率纹理中的1080x1920像素纹理的位置和位置(可能具有不同的宽高比)。我们希望完全填充(“scaleAspectFill”)解析纹理,因此我们将renderedCoordinates保留在片段着色器中。在整个分辨率纹理上定义一个矩形,这意味着为分辨率纹理中的每个像素调用片段着色器。接下来,我们将简单地更改纹理坐标。

  • 让我们将宽高比定义为ratio = width / height,将解析纹理定义为r_tex,将要渲染的纹理定义为tex

  • 因此,假设您的分辨率纹理没有相同的宽高比,则有两种可能的情况:

    1. 要渲染的纹理的宽高比大于,而不是解析纹理的宽高比(金属渲染到的纹理),这意味着您要渲染的纹理宽度大于分辨率纹理。在这种情况下,我们保留坐标的y值。纹理坐标的x值将会更改:

      x_left  = 0 + ((tex.width - r_tex.width) / 2.0)
      x_right = tex_width - ((tex.width - r_tex_width) / 2.0)
      

      这些值必须标准化,因为纹理样本需要的坐标范围为0到1:

      x_left  = x_left / tex.width
      x_right = x_right / tex.width
      

      我们有新的纹理坐标:

      topLeft = float2(x_left,0)
      topRight = float2(x_right,0)
      bottomLeft = float2(x_left,1)
      bottomRight = float2(x_right,1)
      

      这样做的结果是纹理的顶部或底部都不会被切除,但是左侧和右侧的一些外部部分将被剪裁,即不可见。

    2. 要渲染的纹理的宽高比小于,而不是解析纹理的宽高比。该过程与第一个场景相同,但这次我们将更改y坐标

这应该渲染纹理,以便完全填充分辨纹理,并在x轴上保持纹理的纵横比。保持y轴将类似地工作。此外,您必须检查纹理的哪一侧更大/更小,并将其合并到您的计算中。这将剪切部分纹理,就像使用scaleAspectFill时一样。请注意,上述解决方案未经测试。但我希望它有所帮助。请务必不时访问Metal Best Practices文档,这对于正确理解基本概念非常有帮助。与Metal玩得开心!

答案 1 :(得分:0)

因此,您的顶点着色器非常直接地指示源纹理被拉伸到视口的尺寸。您正在渲染填充视口的四边形,因为它的坐标位于标准化设备坐标系的水平和垂直方向的极值([-1,1])。

并且您在同一范围内将源纹理转角映射到角落。这是因为您为纹理坐标指定了纹理坐标空间([0,1])的极值。

有多种方法可以实现您的目标。您可以通过缓冲区将顶点坐标传递到着色器,而不是对它们进行硬编码。这样,您就可以在应用代码中计算适当的值。您可以在渲染目标中计算所需的目标坐标,以NDC表示。所以,从概念上讲,像left_ndc = (left_pixel / target_width) * 2 - 1等等。

或者,可能更容易,您可以将着色器保持原样,并将绘制操作的视口更改为仅定位渲染目标的相应部分。