Introduce Photoshop-like alpha blending

This commit is contained in:
Hajime Hoshi 2014-12-20 05:22:10 +09:00
parent dc0e67f600
commit 6673a39fa2
13 changed files with 155 additions and 71 deletions

View File

@ -32,7 +32,7 @@ func textWidth(str string) int {
return charWidth * len(str) return charWidth * len(str)
} }
func drawText(context ebiten.GraphicsContext, textures *Textures, str string, x, y, scale int, clr color.Color) { func drawText(context ebiten.GraphicsContext, textures *Textures, str string, ox, oy, scale int, clr color.Color) {
fontTextureId := textures.GetTexture("font") fontTextureId := textures.GetTexture("font")
parts := []ebiten.TexturePart{} parts := []ebiten.TexturePart{}
@ -53,11 +53,9 @@ func drawText(context ebiten.GraphicsContext, textures *Textures, str string, x,
locationX += charWidth locationX += charWidth
} }
geoMat := ebiten.GeometryMatrixI() geoMat := ebiten.ScaleGeometry(float64(scale), float64(scale))
geoMat.Concat(ebiten.ScaleGeometry(float64(scale), float64(scale))) geoMat.Concat(ebiten.TranslateGeometry(float64(ox), float64(oy)))
geoMat.Concat(ebiten.TranslateGeometry(float64(x), float64(y))) clrMat := ebiten.ScaleColor(clr)
clrMat := ebiten.ColorMatrixI()
clrMat.Concat(ebiten.ScaleColor(clr))
context.DrawTexture(fontTextureId, parts, geoMat, clrMat) context.DrawTexture(fontTextureId, parts, geoMat, clrMat)
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package ebiten package ebiten
import ( import (
"github.com/hajimehoshi/ebiten/internal"
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/opengl/internal/shader" "github.com/hajimehoshi/ebiten/internal/opengl/internal/shader"
) )
@ -92,11 +93,11 @@ func (r *RenderTarget) Size() (width int, height int) {
} }
func u(x float64, width int) float32 { func u(x float64, width int) float32 {
return float32(x) / float32(opengl.AdjustSizeForTexture(width)) return float32(x) / float32(internal.AdjustSizeForTexture(width))
} }
func v(y float64, height int) float32 { func v(y float64, height int) float32 {
return float32(y) / float32(opengl.AdjustSizeForTexture(height)) return float32(y) / float32(internal.AdjustSizeForTexture(height))
} }
func textureQuads(parts []TexturePart, width, height int) []shader.TextureQuad { func textureQuads(parts []TexturePart, width, height int) []shader.TextureQuad {

View File

@ -22,8 +22,10 @@ import (
) )
func newGraphicsContext(screenWidth, screenHeight, screenScale int) (*graphicsContext, error) { func newGraphicsContext(screenWidth, screenHeight, screenScale int) (*graphicsContext, error) {
// The defualt framebuffer should be 0. r, t, err := opengl.NewZeroRenderTarget(screenWidth*screenScale, screenHeight*screenScale, true)
r := opengl.NewRenderTarget(screenWidth*screenScale, screenHeight*screenScale, true) if err != nil {
return nil, err
}
screen, err := idsInstance.createRenderTarget(screenWidth, screenHeight, gl.NEAREST) screen, err := idsInstance.createRenderTarget(screenWidth, screenHeight, gl.NEAREST)
if err != nil { if err != nil {
@ -32,7 +34,7 @@ func newGraphicsContext(screenWidth, screenHeight, screenScale int) (*graphicsCo
c := &graphicsContext{ c := &graphicsContext{
currents: make([]*RenderTarget, 1), currents: make([]*RenderTarget, 1),
defaultR: &RenderTarget{r, nil}, defaultR: &RenderTarget{r, &Texture{t}},
screen: screen, screen: screen,
screenWidth: screenWidth, screenWidth: screenWidth,
screenHeight: screenHeight, screenHeight: screenHeight,

4
ids.go
View File

@ -67,7 +67,9 @@ func (i *ids) drawTexture(target *RenderTarget, texture *Texture, parts []Textur
} }
projectionMatrix := target.glRenderTarget.ProjectionMatrix() projectionMatrix := target.glRenderTarget.ProjectionMatrix()
quads := textureQuads(parts, glTexture.Width(), glTexture.Height()) quads := textureQuads(parts, glTexture.Width(), glTexture.Height())
shader.DrawTexture(glTexture.Native(), projectionMatrix, quads, &geo, &color) w, h := target.Size()
shader.DrawTexture(glTexture.Native(), target.texture.glTexture.Native(), w, h, projectionMatrix, quads, &geo, &color)
gl.Flush()
return nil return nil
} }

32
internal/math.go Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright 2014 Hajime Hoshi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
func NextPowerOf2(x uint64) uint64 {
x -= 1
x |= (x >> 1)
x |= (x >> 2)
x |= (x >> 4)
x |= (x >> 8)
x |= (x >> 16)
x |= (x >> 32)
return x + 1
}
func AdjustSizeForTexture(size int) int {
return int(NextPowerOf2(uint64(size)))
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package opengl_test package internal_test
import ( import (
. "." . "."
@ -35,8 +35,7 @@ func TestNextPowerOf2(t *testing.T) {
got := NextPowerOf2(testCase.arg) got := NextPowerOf2(testCase.arg)
wanted := testCase.expected wanted := testCase.expected
if wanted != got { if wanted != got {
t.Errorf("Clp(%d) = %d, wanted %d", t.Errorf("Clp(%d) = %d, wanted %d", testCase.arg, got, wanted)
testCase.arg, got, wanted)
} }
} }

View File

@ -18,6 +18,7 @@ package shader
import ( import (
"github.com/go-gl/gl" "github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
"sync" "sync"
) )
@ -38,7 +39,7 @@ type Matrix interface {
} }
// TODO: Use VBO // TODO: Use VBO
func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []TextureQuad, geo Matrix, color Matrix) { func DrawTexture(native gl.Texture, target gl.Texture, width, height int, projectionMatrix [4][4]float64, quads []TextureQuad, geo Matrix, color Matrix) {
once.Do(func() { once.Do(func() {
initialize() initialize()
}) })
@ -47,27 +48,39 @@ func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []Text
return return
} }
// TODO: Check performance // TODO: Check performance
shaderProgram := use(glMatrix(projectionMatrix), geo, color) shaderProgram := use(glMatrix(projectionMatrix), width, height, geo, color)
gl.ActiveTexture(gl.TEXTURE0)
native.Bind(gl.TEXTURE_2D) native.Bind(gl.TEXTURE_2D)
defer gl.Texture(0).Bind(gl.TEXTURE_2D)
gl.ActiveTexture(gl.TEXTURE1)
target.Bind(gl.TEXTURE_2D)
texture0UniformLocation := getUniformLocation(shaderProgram, "texture0")
texture1UniformLocation := getUniformLocation(shaderProgram, "texture1")
texture0UniformLocation.Uniform1i(0)
texture1UniformLocation.Uniform1i(1)
vertexAttrLocation := getAttributeLocation(shaderProgram, "vertex") vertexAttrLocation := getAttributeLocation(shaderProgram, "vertex")
texCoordAttrLocation := getAttributeLocation(shaderProgram, "tex_coord") texCoord0AttrLocation := getAttributeLocation(shaderProgram, "tex_coord0")
texCoord1AttrLocation := getAttributeLocation(shaderProgram, "tex_coord1")
gl.EnableClientState(gl.VERTEX_ARRAY) gl.EnableClientState(gl.VERTEX_ARRAY)
gl.EnableClientState(gl.TEXTURE_COORD_ARRAY) gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
vertexAttrLocation.EnableArray() vertexAttrLocation.EnableArray()
texCoordAttrLocation.EnableArray() texCoord0AttrLocation.EnableArray()
texCoord1AttrLocation.EnableArray()
defer func() { defer func() {
texCoordAttrLocation.DisableArray() texCoord1AttrLocation.DisableArray()
texCoord0AttrLocation.DisableArray()
vertexAttrLocation.DisableArray() vertexAttrLocation.DisableArray()
gl.DisableClientState(gl.TEXTURE_COORD_ARRAY) gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
gl.DisableClientState(gl.VERTEX_ARRAY) gl.DisableClientState(gl.VERTEX_ARRAY)
}() }()
vertices := []float32{} vertices := []float32{}
texCoords := []float32{} texCoords0 := []float32{}
texCoords1 := []float32{}
indicies := []uint32{} indicies := []uint32{}
// TODO: Check len(quads) and gl.MAX_ELEMENTS_INDICES? // TODO: Check len(quads) and gl.MAX_ELEMENTS_INDICES?
for i, quad := range quads { for i, quad := range quads {
@ -85,12 +98,24 @@ func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []Text
u2 := quad.TextureCoordU2 u2 := quad.TextureCoordU2
v1 := quad.TextureCoordV1 v1 := quad.TextureCoordV1
v2 := quad.TextureCoordV2 v2 := quad.TextureCoordV2
texCoords = append(texCoords, texCoords0 = append(texCoords0,
u1, v1, u1, v1,
u2, v1, u2, v1,
u1, v2, u1, v2,
u2, v2, u2, v2,
) )
w := float32(internal.AdjustSizeForTexture(width))
h := float32(internal.AdjustSizeForTexture(height))
xx1 := x1 / w
xx2 := x2 / w
yy1 := y1 / h
yy2 := y2 / h
texCoords1 = append(texCoords1,
xx1, yy1,
xx2, yy1,
xx1, yy2,
xx2, yy2,
)
base := uint32(i * 4) base := uint32(i * 4)
indicies = append(indicies, indicies = append(indicies,
base, base+1, base+2, base, base+1, base+2,
@ -98,6 +123,7 @@ func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []Text
) )
} }
vertexAttrLocation.AttribPointer(2, gl.FLOAT, false, 0, vertices) vertexAttrLocation.AttribPointer(2, gl.FLOAT, false, 0, vertices)
texCoordAttrLocation.AttribPointer(2, gl.FLOAT, false, 0, texCoords) texCoord0AttrLocation.AttribPointer(2, gl.FLOAT, false, 0, texCoords0)
texCoord1AttrLocation.AttribPointer(2, gl.FLOAT, false, 0, texCoords1)
gl.DrawElements(gl.TRIANGLES, len(indicies), gl.UNSIGNED_INT, indicies) gl.DrawElements(gl.TRIANGLES, len(indicies), gl.UNSIGNED_INT, indicies)
} }

View File

@ -18,6 +18,7 @@ package shader
import ( import (
"github.com/go-gl/gl" "github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
) )
type program struct { type program struct {
@ -80,7 +81,7 @@ func getUniformLocation(program gl.Program, name string) gl.UniformLocation {
return location return location
} }
func use(projectionMatrix [16]float32, geo Matrix, color Matrix) gl.Program { func use(projectionMatrix [16]float32, width, height int, geo Matrix, color Matrix) gl.Program {
// TODO: Check the performance. // TODO: Check the performance.
program := programColorMatrix program := programColorMatrix
@ -99,6 +100,15 @@ func use(projectionMatrix [16]float32, geo Matrix, color Matrix) gl.Program {
tx, ty, 0, 1, tx, ty, 0, 1,
} }
getUniformLocation(program.native, "modelview_matrix").UniformMatrix4fv(false, glModelviewMatrix) getUniformLocation(program.native, "modelview_matrix").UniformMatrix4fv(false, glModelviewMatrix)
txn := tx / float32(internal.AdjustSizeForTexture(width))
tyn := ty / float32(internal.AdjustSizeForTexture(height))
glModelviewMatrixN := [...]float32{
a, c, 0, 0,
b, d, 0, 0,
0, 0, 1, 0,
txn, tyn, 0, 1,
}
getUniformLocation(program.native, "modelview_matrix_n").UniformMatrix4fv(false, glModelviewMatrixN)
getUniformLocation(program.native, "texture").Uniform1i(0) getUniformLocation(program.native, "texture").Uniform1i(0)

View File

@ -42,41 +42,53 @@ var shaders = map[shaderId]*shader{
source: ` source: `
uniform mat4 projection_matrix; uniform mat4 projection_matrix;
uniform mat4 modelview_matrix; uniform mat4 modelview_matrix;
uniform mat4 modelview_matrix_n;
attribute vec2 vertex; attribute vec2 vertex;
attribute vec2 tex_coord; attribute vec2 tex_coord0;
varying vec2 vertex_out_tex_coord; attribute vec2 tex_coord1;
varying vec2 vertex_out_tex_coord0;
varying vec2 vertex_out_tex_coord1;
void main(void) { void main(void) {
vertex_out_tex_coord = tex_coord; vertex_out_tex_coord0 = tex_coord0;
vertex_out_tex_coord1 = (modelview_matrix_n * vec4(tex_coord1, 0, 1)).xy;
gl_Position = projection_matrix * modelview_matrix * vec4(vertex, 0, 1); gl_Position = projection_matrix * modelview_matrix * vec4(vertex, 0, 1);
} }
`, `,
}, },
// TODO: Now this shader is not used
shaderFragment: { shaderFragment: {
shaderType: gl.FRAGMENT_SHADER, shaderType: gl.FRAGMENT_SHADER,
source: ` source: `
uniform sampler2D texture; uniform sampler2D texture;
varying vec2 vertex_out_tex_coord; varying vec2 vertex_out_tex_coord0;
void main(void) { void main(void) {
gl_FragColor = texture2D(texture, vertex_out_tex_coord); gl_FragColor = texture2D(texture, vertex_out_tex_coord0);
} }
`, `,
}, },
shaderColorMatrix: { shaderColorMatrix: {
shaderType: gl.FRAGMENT_SHADER, shaderType: gl.FRAGMENT_SHADER,
source: ` source: `
uniform sampler2D texture; uniform sampler2D texture0;
uniform sampler2D texture1;
uniform mat4 color_matrix; uniform mat4 color_matrix;
uniform vec4 color_matrix_translation; uniform vec4 color_matrix_translation;
varying vec2 vertex_out_tex_coord; varying vec2 vertex_out_tex_coord0;
varying vec2 vertex_out_tex_coord1;
void main(void) { void main(void) {
vec4 color = texture2D(texture, vertex_out_tex_coord); vec4 color0 = texture2D(texture0, vertex_out_tex_coord0);
gl_FragColor = (color_matrix * color) + color_matrix_translation; vec4 color1 = texture2D(texture1, vertex_out_tex_coord1);
color0 = (color_matrix * color0) + color_matrix_translation;
gl_FragColor.a = color0.a + color1.a - color0.a * color1.a;
gl_FragColor.rgb = (color0.a * color0.rgb + (1.0 - color0.a) * color1.a * color1.rgb) / gl_FragColor.a;
} }
`, `,
}, },
// TODO: Now this shader is not used
shaderSolidColor: { shaderSolidColor: {
shaderType: gl.FRAGMENT_SHADER, shaderType: gl.FRAGMENT_SHADER,
source: ` source: `

View File

@ -16,6 +16,7 @@ limitations under the License.
package shader package shader
// TODO: Rename X1, X2 -> X0, X1
type TextureQuad struct { type TextureQuad struct {
VertexX1 float32 VertexX1 float32
VertexX2 float32 VertexX2 float32

View File

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/go-gl/gl" "github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
) )
func orthoProjectionMatrix(left, right, bottom, top int) [4][4]float64 { func orthoProjectionMatrix(left, right, bottom, top int) [4][4]float64 {
@ -43,12 +44,23 @@ type RenderTarget struct {
flipY bool flipY bool
} }
func NewRenderTarget(width, height int, flipY bool) *RenderTarget { func NewZeroRenderTarget(width, height int, flipY bool) (*RenderTarget, *Texture, error) {
return &RenderTarget{ r := &RenderTarget{
width: width, width: width,
height: height, height: height,
flipY: flipY, flipY: flipY,
} }
// The framebuffer 0 can't be enlarged, so any filter is acceptable.
t, err := NewTexture(width, height, gl.NEAREST)
if err != nil {
return nil, nil, err
}
// TODO: Does this affect the current rendering target?
gl.Framebuffer(0).Bind()
if err := framebufferTexture(t.native); err != nil {
return nil, nil, err
}
return r, t, err
} }
func NewRenderTargetFromTexture(texture *Texture) (*RenderTarget, error) { func NewRenderTargetFromTexture(texture *Texture) (*RenderTarget, error) {
@ -80,22 +92,28 @@ func (r *RenderTarget) Dispose() {
} }
func createFramebuffer(nativeTexture gl.Texture) (gl.Framebuffer, error) { func createFramebuffer(nativeTexture gl.Texture) (gl.Framebuffer, error) {
// TODO: Does this affect the current rendering target?
framebuffer := gl.GenFramebuffer() framebuffer := gl.GenFramebuffer()
framebuffer.Bind() framebuffer.Bind()
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, nativeTexture, 0) if err := framebufferTexture(nativeTexture); err != nil {
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE { return 0, err
return 0, errors.New("creating framebuffer failed")
} }
// Set this framebuffer opaque because alpha values on a target might be
// confusing.
gl.ClearColor(0, 0, 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
return framebuffer, nil return framebuffer, nil
} }
func framebufferTexture(nativeTexture gl.Texture) error {
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, nativeTexture, 0)
if gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE {
return errors.New("creating framebuffer failed")
}
gl.ClearColor(0, 0, 0, 0)
gl.Clear(gl.COLOR_BUFFER_BIT)
return nil
}
func (r *RenderTarget) SetAsViewport() error { func (r *RenderTarget) SetAsViewport() error {
gl.Flush() gl.Flush()
r.framebuffer.Bind() r.framebuffer.Bind()
@ -104,21 +122,19 @@ func (r *RenderTarget) SetAsViewport() error {
return errors.New(fmt.Sprintf("glBindFramebuffer failed: %d", err)) return errors.New(fmt.Sprintf("glBindFramebuffer failed: %d", err))
} }
gl.BlendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE) width := internal.AdjustSizeForTexture(r.width)
height := internal.AdjustSizeForTexture(r.height)
width := AdjustSizeForTexture(r.width)
height := AdjustSizeForTexture(r.height)
gl.Viewport(0, 0, width, height) gl.Viewport(0, 0, width, height)
return nil return nil
} }
func (r *RenderTarget) ProjectionMatrix() [4][4]float64 { func (r *RenderTarget) ProjectionMatrix() [4][4]float64 {
width := AdjustSizeForTexture(r.width) width := internal.AdjustSizeForTexture(r.width)
height := AdjustSizeForTexture(r.height) height := internal.AdjustSizeForTexture(r.height)
m := orthoProjectionMatrix(0, width, 0, height) m := orthoProjectionMatrix(0, width, 0, height)
if r.flipY { if r.flipY {
m[1][1] *= -1 m[1][1] *= -1
m[1][3] += float64(r.height) / float64(AdjustSizeForTexture(r.height)) * 2 m[1][3] += float64(r.height) / float64(internal.AdjustSizeForTexture(r.height)) * 2
} }
return m return m
} }

View File

@ -18,32 +18,18 @@ package opengl
import ( import (
"github.com/go-gl/gl" "github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal"
"image" "image"
"image/draw" "image/draw"
) )
func NextPowerOf2(x uint64) uint64 {
x -= 1
x |= (x >> 1)
x |= (x >> 2)
x |= (x >> 4)
x |= (x >> 8)
x |= (x >> 16)
x |= (x >> 32)
return x + 1
}
func AdjustSizeForTexture(size int) int {
return int(NextPowerOf2(uint64(size)))
}
func adjustImageForTexture(img image.Image) *image.NRGBA { func adjustImageForTexture(img image.Image) *image.NRGBA {
width, height := img.Bounds().Size().X, img.Bounds().Size().Y width, height := img.Bounds().Size().X, img.Bounds().Size().Y
adjustedImageBounds := image.Rectangle{ adjustedImageBounds := image.Rectangle{
image.ZP, image.ZP,
image.Point{ image.Point{
AdjustSizeForTexture(width), internal.AdjustSizeForTexture(width),
AdjustSizeForTexture(height), internal.AdjustSizeForTexture(height),
}, },
} }
if nrgba, ok := img.(*image.NRGBA); ok && img.Bounds() == adjustedImageBounds { if nrgba, ok := img.(*image.NRGBA); ok && img.Bounds() == adjustedImageBounds {
@ -95,8 +81,8 @@ func createNativeTexture(textureWidth, textureHeight int, pixels []uint8, filter
} }
func NewTexture(width, height int, filter int) (*Texture, error) { func NewTexture(width, height int, filter int) (*Texture, error) {
w := AdjustSizeForTexture(width) w := internal.AdjustSizeForTexture(width)
h := AdjustSizeForTexture(height) h := internal.AdjustSizeForTexture(height)
native := createNativeTexture(w, h, nil, filter) native := createNativeTexture(w, h, nil, filter)
return &Texture{native, width, height}, nil return &Texture{native, width, height}, nil
} }

1
ui.go
View File

@ -52,7 +52,6 @@ func init() {
currentUI.use(func() { currentUI.use(func() {
gl.Init() gl.Init()
gl.Enable(gl.TEXTURE_2D) gl.Enable(gl.TEXTURE_2D)
gl.Enable(gl.BLEND)
}) })
} }